# rate-limit-middleware _(no submodules)_ ## Purpose Provides framework-agnostic middleware factory helpers that wrap `createRateLimiter` for easy integration into HTTP servers, API gateways, and custom request pipelines. All middleware functions accept `req: unknown` and return a `Promise`, keeping them decoupled from any specific framework (Hono, Express, Fastify, Bun's native server, etc.). The three exported helpers cover the most common patterns: a simple checker function, IP-based limiting, and route-specific per-path limits. For Hono-specific wiring, consumers can use a thin adapter that reads `result.allowed` and returns a `429` response. ## Features - `createRateLimitChecker` — returns a `(context) => Promise` wrapper - `getRateLimitResult` — low-level direct storage+algorithm call - `createIpRateLimiter` — IP-key extraction with configurable `keyExtractor`, `skip`, `onLimited` - `createRouteRateLimiter` — per-route config map with route-prefix matching - All factories accept an optional `skip` predicate to bypass limiting for health probes - `onLimited` callback for telemetry, logging, or 429-response generation ## Usage: Basic Example ```ts import { createRateLimitChecker } from "@suaveplan/rate-limiter"; const check = createRateLimitChecker({ defaultConfig: { limit: 100, window: 60_000 }, }); const result = await check({ key: "user:42" }); if (!result.allowed) { return new Response("Too Many Requests", { status: 429 }); } ``` ## Usage: Advanced Example ```ts import { createIpRateLimiter, createRouteRateLimiter, MemoryStorage } from "@suaveplan/rate-limiter"; const storage = new MemoryStorage({ maxSize: 10_000 }); // IP-based limiter that skips health checks const ipLimiter = createIpRateLimiter( { limit: 200, window: 60_000 }, storage, { keyExtractor: (req) => (req as Request).headers.get("x-forwarded-for") ?? "unknown", skip: (req) => (req as Request).url.endsWith("/health"), onLimited: (result) => console.warn("IP limited", result), } ); // Route-specific limits const routeLimiter = createRouteRateLimiter( { "/api/auth/login": { limit: 5, window: 60_000 }, "/api/search": { limit: 50, window: 10_000 }, }, storage ); const result = await ipLimiter(request); if (!result.allowed) { return new Response("Too Many Requests", { status: 429 }); } ``` ## API Reference ### `createRateLimitChecker(options)` `(options: RateLimiterOptions) => (context: RateLimitContext) => Promise` ### `getRateLimitResult(context, storage, config)` `(context: RateLimitContext, storage: RateLimitStorage, config: RateLimitConfig) => Promise` Low-level helper — creates a fresh `SlidingWindow` per call, restoring persisted state from storage. ### `createIpRateLimiter(config, storage?, middlewareOptions?)` Returns `(req: unknown) => Promise`. ### `createRouteRateLimiter(routes, storage?, middlewareOptions?)` Returns `(req: unknown) => Promise`. Matches route prefixes in insertion order. ### `RateLimiterMiddlewareOptions` | Field | Type | Description | |---|---|---| | `keyExtractor?` | `(req: unknown) => string` | Extract key from request | | `onLimited?` | `(result: RateLimitResult) => void` | Called on every check (use to send 429) | | `skip?` | `(req: unknown) => boolean` | Skip limiting for matching requests | ## Implementation Notes `createIpRateLimiter` and `createRouteRateLimiter` each construct one `createRateLimiter` instance per route/IP config at factory time, sharing a single storage adapter. Route matching in `createRouteRateLimiter` uses `key.startsWith(route) || key.includes(route)` — for production use with complex routing rules, consumers should provide a custom `keyExtractor` that returns the matched route prefix.