/** * A dependency-free fixed-window rate limiter keyed by an opaque string (client IP, * account email, staff id, …). Returns true while the key is under `limit` in the * current `windowMs` window, false once it is exhausted. Expired buckets are swept * lazily so the map cannot grow without bound under key rotation. In its own module * so both the api router and the public-share route can share it without a cycle. */ export function makeRateLimiter(limit: number, windowMs: number): (key: string, now?: number) => boolean { const buckets = new Map() return (key: string, now = Date.now()): boolean => { if (buckets.size > 5000) for (const [k, b] of buckets) if (now >= b.resetAt) buckets.delete(k) const bucket = buckets.get(key) if (bucket === undefined || now >= bucket.resetAt) { buckets.set(key, { count: 1, resetAt: now + windowMs }) return true } if (bucket.count >= limit) return false bucket.count += 1 return true } }