// Per-(slug, IP) token bucket. 30 requests in a 10s window. Cheap, in-memory, // resets on process restart. Behind a load balancer each replica enforces its // own bucket - that's fine, the goal is "stop a hostile script from melting one // box", not perfect distributed accounting. interface Bucket { tokens: number; refillAt: number } const buckets = new Map(); const CAPACITY = 30; const WINDOW_MS = 10_000; export function allow(slug: string, ip: string): boolean { const key = `${slug}\x00${ip}`; const now = Date.now(); let b = buckets.get(key); if (!b || now > b.refillAt) { b = { tokens: CAPACITY, refillAt: now + WINDOW_MS }; buckets.set(key, b); } if (b.tokens <= 0) return false; b.tokens--; return true; } // Periodic sweep so the map doesn't grow forever. setInterval(() => { const now = Date.now(); for (const [k, b] of buckets) { if (now > b.refillAt + WINDOW_MS) buckets.delete(k); } }, 60_000).unref?.();