Implement rateLimiter(timestamps, windowMs, maxRequests). You're given a chronologically-sorted array of past request timestamps (milliseconds, ascending), a sliding window size, and a per-window cap. Return the subset of timestamps that would have been allowed through — the rest were rejected for blowing the quota.
The rule is the sliding-window version of rate limiting: for each timestamp t, count how many of the previously-allowed timestamps fall within (t - windowMs, t]. If that count is less than maxRequests, allow t. Otherwise reject it. Crucially, the count is over allowed-only history — rejected requests do not consume the quota of later windows.
// timestamps: number[] — sorted ascending, ms since epoch
// windowMs: number — sliding window size in ms
// maxRequests: number — max allowed per window
// returns: number[] — the subset of timestamps that were ALLOWED,
// in the same order as input
function rateLimiter(timestamps: number[], windowMs: number, maxRequests: number): number[];
// Three per second, all bursting in 600ms — first 3 allowed, rest rejected.
rateLimiter([100, 200, 300, 400, 500, 600], 1000, 3);
// → [100, 200, 300]
// Spread far apart — every request fits its own window.
rateLimiter([0, 2000, 4000, 6000], 1000, 2);
// → [0, 2000, 4000, 6000]
// Tied timestamps — the first `max` of the tie are allowed; later ones rejected.
rateLimiter([100, 100, 100, 100], 1000, 2);
// → [100, 100]
t is (t - windowMs, t]. A previously-allowed timestamp exactly equal to t - windowMs is out of the window. Pick a side, document it — we picked left-exclusive because it matches how most rate-limit systems treat "now minus window" as already aged-out.timestamps is ascending. If it isn't, behavior is undefined — don't sort defensively, that hides bugs at the caller.maxRequests of the tie are allowed; the rest rejected.timestamps in the same order — never reordered, never reshuffled.