/** * Sliding-window rate limit. * * Scope: cheap protection for unauthenticated webhook surfaces (Meta inbound, * billing-gateway callbacks, admin endpoints). The shape: per key, count the * number of hits in the last N seconds; reject when the count crosses `max`. * * Storage is pluggable via `RateLimitStore`. Ship two implementations: * * - `KvRateLimitStore({ kv, prefix })` — durable across isolates, the right * default for Workers. Writes use `waitUntil`-friendly TTL. * - `MemoryRateLimitStore()` — per-isolate Map. For tests; also fine for * coarse single-isolate caps where exact accuracy doesn't matter. * * Hono integration: * * const limiter = new RateLimit({ * store: new KvRateLimitStore({ kv: env.KV, prefix: 'rl:webhook' }), * windowSeconds: 60, * max: 30, * }); * app.post('/webhook', honoRateLimit(limiter), webhookHandler); * * Out of scope: distributed Redis, leaky-bucket smoothing, IP geolocation. * If you need any of that, plug in a custom store. The contract is small — * `get(key) → number[]` + `put(key, hits, ttlSeconds) → void`. */ import type { MiddlewareHandler } from 'hono'; export interface RateLimitStore { get(key: string): Promise; put(key: string, hits: number[], ttlSeconds: number): Promise; } export interface RateLimitResult { allowed: boolean; /** Hit count inside the current window AFTER this check (decremented on reject). */ count: number; /** Seconds the caller should wait before retrying. 0 when allowed. */ retryAfter: number; } export interface RateLimitOptions { store: RateLimitStore; /** Rolling window in seconds. */ windowSeconds: number; /** Max hits allowed inside the window. */ max: number; } export declare class RateLimit { readonly store: RateLimitStore; readonly windowSeconds: number; readonly max: number; constructor({ store, windowSeconds, max }: RateLimitOptions); /** * Check + record. Returns `{ allowed, count, retryAfter }`. Persists the * new hit only when `allowed` is true. Failure-safe: a store throw is * treated as "allowed" (fail-open) so a KV outage can't take the bot * down — the same policy as `Blocklist`. */ check(key: string): Promise; } export interface KvRateLimitStoreOptions { kv: KVNamespace; prefix?: string; } export declare class KvRateLimitStore implements RateLimitStore { readonly kv: KVNamespace; readonly prefix: string; constructor({ kv, prefix }: KvRateLimitStoreOptions); private fullKey; get(key: string): Promise; put(key: string, hits: number[], ttlSeconds: number): Promise; } export declare class MemoryRateLimitStore implements RateLimitStore { private readonly map; get(key: string): Promise; put(key: string, hits: number[], ttlSeconds: number): Promise; /** Test helper — clears all keys. */ clear(): void; } export interface HonoRateLimitOptions { /** Override the key extractor. Default: `cf-connecting-ip + req.path`. */ keyFn?: (c: HonoRateLimitContext) => string; /** * Override the on-reject response. Default: `c.json({ error: 'rate_limited', * retry_after_seconds }, 429)`. */ onReject?: (c: HonoRateLimitContext, result: RateLimitResult) => Response | Promise; } /** Subset of the Hono context the middleware touches. */ export interface HonoRateLimitContext { req: { header(name: string): string | undefined; path: string; }; json(body: unknown, status?: number): Response; } /** * Hono middleware that runs `limit.check(keyFn(c))` before the handler. * * Default key: `cf-connecting-ip + req.path`. Default reject: a 429 with * `{ error: 'rate_limited', retry_after_seconds }`. Both overridable. */ export declare function honoRateLimit(limit: RateLimit, { keyFn, onReject }?: HonoRateLimitOptions): MiddlewareHandler; //# sourceMappingURL=rate_limit.d.ts.map