interface RateLimitEntry { count: number resetAt: number } const store = new Map() // Cleanup stale entries every 60 seconds setInterval(() => { const now = Date.now() for (const [key, entry] of store) { if (entry.resetAt <= now) store.delete(key) } }, 60_000) export interface RateLimitConfig { /** Max requests in the window */ limit: number /** Window size in seconds */ windowSec: number } export interface RateLimitResult { allowed: boolean remaining: number resetAt: number } export function checkRateLimit(key: string, config: RateLimitConfig): RateLimitResult { const now = Date.now() const entry = store.get(key) if (!entry || entry.resetAt <= now) { store.set(key, { count: 1, resetAt: now + config.windowSec * 1000 }) return { allowed: true, remaining: config.limit - 1, resetAt: now + config.windowSec * 1000 } } if (entry.count >= config.limit) { return { allowed: false, remaining: 0, resetAt: entry.resetAt } } entry.count++ return { allowed: true, remaining: config.limit - entry.count, resetAt: entry.resetAt } } // Preset configurations export const RATE_LIMITS = { magicLink: { limit: 3, windowSec: 3600 } as RateLimitConfig, // 3 per email per hour authVerify: { limit: 10, windowSec: 900 } as RateLimitConfig, // 10 per IP per 15min api: { limit: 60, windowSec: 60 } as RateLimitConfig, // 60 per session per minute chat: { limit: 20, windowSec: 60 } as RateLimitConfig, // 20 per session per minute } as const