export class SiloFixedWindowRateLimiter { private windowStartedAt: number; private used = 0; constructor(readonly limit: number, readonly windowMs: number, now = Date.now()) { if (!Number.isSafeInteger(limit) || limit <= 0) throw new Error("rate limit must be a positive integer"); if (!Number.isSafeInteger(windowMs) || windowMs <= 0) throw new Error("rate window must be a positive integer"); this.windowStartedAt = now; } consume(now = Date.now()): { readonly allowed: true } | { readonly allowed: false; readonly retryAfterSeconds: number } { if (now >= this.windowStartedAt + this.windowMs) { this.windowStartedAt = now; this.used = 0; } if (this.used >= this.limit) { return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil((this.windowStartedAt + this.windowMs - now) / 1000)), }; } this.used += 1; return { allowed: true }; } }