/** * Lemma API v2 — per-tenant rate limiter (Lane D) * * Fixed-window counter keyed by `tenantId + windowBucket`. Exceeding the window * limit yields a 429 with `Retry-After` (backoff until the bucket ends). The * window is derived from `windowMs` and an injectable clock so tests are * deterministic. Fixed window (not sliding) is a deliberate trade: it allows a * brief 2x burst at the boundary in exchange for a cheap, exactly-computable * `Retry-After` (until the next bucket start). */ import type { Pool } from 'pg'; export interface RateLimitDecision { allowed: boolean; used: number; limit: number; remaining: number; /** Epoch ms when the current window ends and the counter resets. */ resetAt: number; } export interface RateLimiter { consume(tenantId: string, limit: number, windowMs: number, now?: number): Promise; } /** The 429 body for rate_limited (additive to the LemmaApiError envelope). */ export interface RateLimitErrorBody { error: { code: 'rate_limited'; status: 429; message: string; limit: number; used: number; remaining: number; retryAfter: number; resetsAt: number; upgradeUrl: string; }; } export declare function rateLimitErrorBody(decision: RateLimitDecision, retryAfterSeconds: number, upgradeUrl: string): RateLimitErrorBody; /** In-process fixed-window limiter. Suitable for single-instance dev and tests. */ export declare class InMemoryRateLimiter implements RateLimiter { private readonly buckets; consume(tenantId: string, limit: number, windowMs: number, now?: number): Promise; } /** Postgres-backed fixed-window limiter, atomic via a single upsert. */ export declare class PostgresRateLimiter implements RateLimiter { private readonly pool; constructor(pool: Pool); consume(tenantId: string, limit: number, windowMs: number, now?: number): Promise; } //# sourceMappingURL=RateLimiter.d.ts.map