interface Bucket { count: number resetAt: number } /** * Minimal fixed-window limiter for the public lead endpoint. In-memory on * purpose: a single landing page behind one Node process does not warrant a * shared store, and losing the counters on restart is harmless. */ export function createRateLimiter(options: { limit: number; windowMs: number }) { const buckets = new Map() return function check(key: string): { allowed: boolean; retryAfterSeconds: number } { const now = Date.now() if (buckets.size > 5000) { for (const [k, bucket] of buckets) if (bucket.resetAt <= now) buckets.delete(k) } const bucket = buckets.get(key) if (!bucket || bucket.resetAt <= now) { buckets.set(key, { count: 1, resetAt: now + options.windowMs }) return { allowed: true, retryAfterSeconds: 0 } } bucket.count += 1 return { allowed: bucket.count <= options.limit, retryAfterSeconds: Math.ceil((bucket.resetAt - now) / 1000), } } }