Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# rate-limit-middleware
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<RateLimitResult>`, 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<RateLimitResult>` 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<RateLimitResult>`
### `getRateLimitResult(context, storage, config)`
`(context: RateLimitContext, storage: RateLimitStorage, config: RateLimitConfig) => Promise<RateLimitResult>`
Low-level helper — creates a fresh `SlidingWindow` per call, restoring persisted state from storage.
### `createIpRateLimiter(config, storage?, middlewareOptions?)`
Returns `(req: unknown) => Promise<RateLimitResult>`.
### `createRouteRateLimiter(routes, storage?, middlewareOptions?)`
Returns `(req: unknown) => Promise<RateLimitResult>`. 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.