// Tiny in-memory sliding-window rate limiter. // Acceptable for single-instance Nitro on Netlify; swap to Mongo/Upstash if // we move to multi-instance. const buckets = new Map() export function rateLimit(key, { max, windowMs }) { // Bypass in test/dev opt-in mode so parallel E2E runs from a single IP // (127.0.0.1) don't exhaust per-IP/email budgets. Mirrors the gate used by // /api/dev/* endpoints and server/middleware/03.rate-limit.js. if (process.env.ALLOW_DEV_TEST_ENDPOINTS === 'true') return true const now = Date.now() // Probabilistic sweep: ~1% of calls evict keys whose newest entry has fully // aged out, so the Map doesn't grow unbounded under random-key spraying. if (Math.random() < 0.01) { for (const [k, arr] of buckets) { const last = arr[arr.length - 1] if (last === undefined || now - last >= windowMs) buckets.delete(k) } } const arr = (buckets.get(key) || []).filter((t) => now - t < windowMs) if (arr.length >= max) { buckets.set(key, arr) return false } arr.push(now) buckets.set(key, arr) return true } // Test helper — clears all buckets so each test starts clean. export function resetRateLimit() { buckets.clear() }