import { RateLimiterMemory } from "rate-limiter-flexible"; // In-memory limiters (no Redis). Fine for a single-instance Plesk deployment; // swap for RateLimiterPostgres if the app is ever scaled to multiple nodes. const limiters = new Map(); function getLimiter(name: string, points: number, durationSec: number): RateLimiterMemory { let limiter = limiters.get(name); if (!limiter) { limiter = new RateLimiterMemory({ points, duration: durationSec }); limiters.set(name, limiter); } return limiter; } export interface RateLimitResult { ok: boolean; retryAfterSec?: number; } /** Consume one unit for `key` under a named bucket; returns ok=false when throttled. */ export async function rateLimit( bucket: string, key: string, opts: { points: number; durationSec: number } ): Promise { const limiter = getLimiter(bucket, opts.points, opts.durationSec); try { await limiter.consume(key); return { ok: true }; } catch (res) { const retryAfterSec = Math.ceil((res as { msBeforeNext?: number }).msBeforeNext ?? 1000) / 1000; return { ok: false, retryAfterSec: Math.ceil(retryAfterSec) }; } } // Common presets. export const LIMITS = { generation: { points: 10, durationSec: 60 }, // 10 generations / min / user repurpose: { points: 15, durationSec: 60 }, api: { points: 60, durationSec: 60 }, // 60 API calls / min / key (writes) read: { points: 120, durationSec: 60 }, // 120 read/list calls / min / key stream: { points: 30, durationSec: 60 }, // SSE (re)connects / min / user } as const;