Initial Commit
This commit is contained in:
101
packages/api-stack/api/api-rate-limit/README.md
Normal file
101
packages/api-stack/api/api-rate-limit/README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# @suaveplan/api-rate-limit — per-route / per-principal / per-tenant / per-IP token-bucket middleware for the @suaveplan/api plugin system. Implements the RequestRateLimiter contract consumed by @suaveplan/api-host's request pipeline and provides a Hono-shaped Middleware that returns 429 + Retry-After on denial. Backed by @suaveplan/rate-limiter's TokenBucket. In-memory store ships by default; the RateLimitStoreLike interface keeps Redis/distributed adapters pluggable.
|
||||
|
||||
Per-route / per-principal / per-tenant / per-IP token-bucket rate-limit
|
||||
middleware for `@suaveplan/api`. Implements the
|
||||
`RequestRateLimiter` contract consumed by `@suaveplan/api-host`'s
|
||||
per-request pipeline (design doc §6 step 4a) and ships a richer
|
||||
`check`-based middleware surface for adapter integrations that want the
|
||||
429 response shape pre-computed.
|
||||
|
||||
Status: Stage-8 keystone (`0.1.0`). In-memory store ships by default;
|
||||
the `RateLimitStoreLike` interface keeps a future Redis adapter
|
||||
pluggable without changing call sites.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `RATELIMIT.*` error-code namespace for the `@suaveplan/api-rate-limit` package and the `RateLimitErr…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `RATELIMIT.*` error-code namespace for the `@suaveplan/api-rate-limit` package and the `RateLimitErr…
|
||||
- [instruments](./src/instruments/instruments.md) — Owns the rate-limit OTel instrument registry. Today the package emits exactly one counter — `api.rate_limit.exceeded_to…
|
||||
- [instruments](./src/instruments/instruments.md) — Owns the rate-limit OTel instrument registry. Today the package emits exactly one counter — `api.rate_limit.exceeded_to…
|
||||
- [middleware](./src/middleware/middleware.md) — The package's centerpiece. `createRateLimitMiddleware` builds a per-request gate around the token-bucket store; the gat…
|
||||
- [middleware](./src/middleware/middleware.md) — The package's centerpiece. `createRateLimitMiddleware` builds a per-request gate around the token-bucket store; the gat…
|
||||
- [selector](./src/selector/selector.md) — Resolves the rate-limit bucket key for a per-request frame, given the route-declared (or host-defaulted) `RateLimitKeyS…
|
||||
- [selector](./src/selector/selector.md) — Resolves the rate-limit bucket key for a per-request frame, given the route-declared (or host-defaulted) `RateLimitKeyS…
|
||||
- [memory-store](./src/store/memory-store/memory-store.md) — Provides the default in-memory implementation of `RateLimitStoreLike`. Backed by `TokenBucket` from `@suaveplan/rate-li…
|
||||
- [memory-store](./src/store/memory-store/memory-store.md) — Provides the default in-memory implementation of `RateLimitStoreLike`. Backed by `TokenBucket` from `@suaveplan/rate-li…
|
||||
- [store-like](./src/store/store-like/store-like.md) — Declares the `RateLimitStoreLike` interface — the storage abstraction behind the per-route token-bucket middleware. Kee…
|
||||
- [store-like](./src/store/store-like/store-like.md) — Declares the `RateLimitStoreLike` interface — the storage abstraction behind the per-route token-bucket middleware. Kee…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why this package exists
|
||||
|
||||
The host pipeline's `RequestRateLimiter.consume(key, weight)` slot is
|
||||
synchronous and intentionally minimal. Plugins want richer policy —
|
||||
`{ rpm, burst, key }` per route, plus a way for the host to declare
|
||||
global defaults in `host.json`. This package owns both ends: the
|
||||
token-bucket store, the key-selector resolution, the OTel counter
|
||||
emission, and the projection back onto the host's narrow `consume`
|
||||
shape.
|
||||
|
||||
## Modules
|
||||
|
||||
| Subpath | Surface |
|
||||
|---|---|
|
||||
| `@suaveplan/api-rate-limit` | Aggregate barrel — every public symbol. |
|
||||
| `@suaveplan/api-rate-limit/middleware` | `createRateLimitMiddleware`, `RateLimitRouteOption`, `RateLimitDecision`, `HostRateLimitDefaults`, `RateLimitRouteOptionSchema`. |
|
||||
| `@suaveplan/api-rate-limit/selector` | `RateLimitKeySelector`, `resolveRateLimitKey`, `RateLimitContext`. |
|
||||
| `@suaveplan/api-rate-limit/store` | `RateLimitStoreLike`, `MemoryRateLimitStore`, `createMemoryRateLimitStore`, `RateLimitBudget`, `RateLimitTakeResult`. |
|
||||
| `@suaveplan/api-rate-limit/instruments` | `createRateLimitInstruments`, `RATE_LIMIT_COUNTER_NAMES`. |
|
||||
| `@suaveplan/api-rate-limit/error-codes` | `RATELIMIT_CODES`, `RateLimitError`, `RateLimitCode`. |
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { createRateLimitMiddleware } from "@suaveplan/api-rate-limit";
|
||||
|
||||
const mw = createRateLimitMiddleware({
|
||||
hostDefaults: {
|
||||
perTenant: { rpm: 600, burst: 30 },
|
||||
perIp: { rpm: 60, burst: 10 },
|
||||
},
|
||||
});
|
||||
|
||||
const decision = mw.check({
|
||||
plugin: "billing",
|
||||
route: "POST /invoices",
|
||||
option: { rpm: 60, burst: 5, key: "principal" },
|
||||
ctx: requestFrame,
|
||||
});
|
||||
if (!decision.allowed) {
|
||||
return new Response(null, { status: 429, headers: decision.headers });
|
||||
}
|
||||
```
|
||||
|
||||
## Wiring into `@suaveplan/api-host`
|
||||
|
||||
```ts
|
||||
import { createRateLimitMiddleware } from "@suaveplan/api-rate-limit";
|
||||
|
||||
const mw = createRateLimitMiddleware({ /* opts */ });
|
||||
const host = createHost({
|
||||
rateLimit: mw.asRequestRateLimiter({
|
||||
plugin: "billing",
|
||||
route: "POST /invoices",
|
||||
option: { rpm: 60, burst: 5 },
|
||||
keyKind: "principal",
|
||||
}),
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Acceptance
|
||||
|
||||
Pre-archive gate:
|
||||
|
||||
```
|
||||
bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-rate-limit
|
||||
```
|
||||
|
||||
100% statements/branches/functions/lines (Istanbul).
|
||||
|
||||
Reference in New Issue
Block a user