UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

59 lines 2.22 kB
function assertPositiveInteger(name, value) { if (!Number.isInteger(value) || value <= 0) { throw new Error(`${name} must be a positive integer`); } } function toRetryAfterSeconds(resetAt) { return Math.max(0, Math.ceil((resetAt - Date.now()) / 1000)); } /** * Create an in-memory rate limiter for tests, examples, and single-process * development. * * This adapter is not durable or distributed. Production apps should use a * provider backed by a shared atomic store when multiple processes or regions * can serve requests. * * @returns A rate-limit port backed by a local `Map`. */ export function createMemoryRateLimiter() { const windows = new Map(); const sweepIntervalMs = 60_000; let nextSweepAt = Date.now() + sweepIntervalMs; return { async hit({ key, limit, windowSec }) { assertPositiveInteger("limit", limit); assertPositiveInteger("windowSec", windowSec); const now = Date.now(); // Lazy sweep: distinct keys would otherwise accumulate expired windows // forever. Prune them at most once per sweep interval on the hit path. if (now >= nextSweepAt) { for (const [existingKey, existing] of windows) { if (existing.resetAt <= now) { windows.delete(existingKey); } } nextSweepAt = now + sweepIntervalMs; } const current = windows.get(key); const window = current && current.resetAt > now ? current : { count: 0, resetAt: now + windowSec * 1000, }; window.count += 1; windows.set(key, window); const allowed = window.count <= limit; const remaining = Math.max(0, limit - window.count); const resetAt = new Date(window.resetAt); return { allowed, remaining, resetAt, retryAfterSeconds: allowed ? null : toRetryAfterSeconds(window.resetAt), }; }, }; } //# sourceMappingURL=rate-limit.js.map