import type { FastifyRequest, preHandlerHookHandler } from "fastify"; type Awaitable = T | Promise; export interface RateLimitQuota { limit: number; windowMs: number; } export interface RateLimitRule extends RateLimitQuota { /** A stable action name, for example `auth.login` or `messages.send`. */ scope: string; /** The independently enforced identity dimension. */ dimension: "actor" | "ip" | (string & {}); /** The raw identity. Stores receive only its hash. */ identity: string; } export interface RateLimitConsumeInput extends RateLimitQuota { key: string; now: number; } export interface RateLimitDecision extends RateLimitQuota { key: string; allowed: boolean; remaining: number; resetAt: number; reason?: "limit" | "store_capacity"; } /** * Distributed implementations must make consume atomic for a key. The contract is * deliberately independent of Fastify so a Redis or database implementation can * be substituted without changing route hooks. */ export interface RateLimitStore { consume(input: RateLimitConsumeInput): Promise; close?(): Awaitable; } export interface LocalRateLimitStoreOptions { maxEntries?: number; cleanupIntervalMs?: number; } /** * Bounded, process-local fixed-window store. It is appropriate for a single server * and intentionally fails closed when its key budget is exhausted. Use a shared * RateLimitStore implementation when several server processes are deployed. */ export declare class LocalRateLimitStore implements RateLimitStore { private readonly buckets; private readonly maxEntries; private readonly cleanupIntervalMs; private lastCleanupAt; constructor(options?: LocalRateLimitStoreOptions); get size(): number; consume(input: RateLimitConsumeInput): Promise; sweep(now: number): number; close(): void; private earliestResetAt; } export interface RateLimitEvaluation { allowed: boolean; decisions: RateLimitDecision[]; bypassedBecauseStoreFailed: boolean; evaluatedAt: number; } export interface HttpRateLimiterOptions { /** Defaults to false. Authentication and mutation limits should remain fail-closed. */ failOpen?: boolean; onStoreError?: (error: unknown) => void; now?: () => number; } export declare class RateLimitUnavailableError extends Error { readonly cause: unknown; constructor(cause: unknown); } export declare class HttpRateLimiter { private readonly store; private readonly failOpen; private readonly onStoreError?; private readonly now; constructor(store: RateLimitStore, options?: HttpRateLimiterOptions); evaluate(rules: readonly RateLimitRule[]): Promise; close(): Promise; } export interface RequestRateLimitPolicy { scope: string; ip?: RateLimitQuota | false; actor?: RateLimitQuota | false; } export interface RateLimitHookOptions { limiter: HttpRateLimiter; policy: RequestRateLimitPolicy | ((request: FastifyRequest) => Awaitable); actorId?: (request: FastifyRequest) => Awaitable; } /** * Builds independent actor and IP rules. `request.ip` is intentional: Fastify * applies its configured `trustProxy` boundary before exposing this value, so the * hook never trusts X-Forwarded-For on its own. */ export declare function requestRateLimitRules(request: Pick, policy: RequestRateLimitPolicy, actorId?: string): RateLimitRule[]; export declare function createRateLimitHook(options: RateLimitHookOptions): preHandlerHookHandler; export declare function createRateLimitStorageKey(rule: Pick): string; export {}; //# sourceMappingURL=rate-limit.d.ts.map