3.1 KiB
rate-limit
(no submodules)
Purpose
The rate-limit module ships tenantRateLimit(options) — a Hono
middleware that throttles requests per resolved tenant. The store is
pluggable via the RateLimitStore interface; the default
MemoryRateLimitStore is bounded by maxEntries (default 10 000)
with LRU eviction on overflow so a high-cardinality tenant set
cannot leak memory.
Tenant-less requests pass through untouched — the gate only activates
once createTenantMiddleware has populated c.get("tenant").
Exceeding the window returns a 429 with Retry-After plus the
standard X-RateLimit-* triple. The body carries the
TENANT.RATE_LIMITED code so consumers can branch on the typed
identifier rather than the HTTP status alone.
Features
- Pluggable backing store via
RateLimitStoreinterface (get,set,delete). Adapters for Redis or SQL plug in via the same shape. - Default
MemoryRateLimitStore— bounded bymaxEntries, LRU eviction, in-process only. - Sliding-fixed window — counter resets at
now + windowMs; the next request afterresetAtstarts a fresh window. - 429 response carries
Retry-After,X-RateLimit-{Limit,Remaining, Reset}, and a JSON body withcode: "TENANT.RATE_LIMITED". - Injectable
clock—FakeClockinterop, deterministic tests.
Usage: Basic
import { tenantRateLimit } from "@suaveplan/api-tenant";
app.use(
"*",
tenantRateLimit({ maxRequests: 60, windowMs: 60_000 })
);
Usage: Advanced
Plugging in a Redis-backed store and a custom key prefix:
import {
type RateLimitRecord,
type RateLimitStore,
tenantRateLimit,
} from "@suaveplan/api-tenant";
const redisStore: RateLimitStore = {
async get(key) {
const raw = await redis.get(key);
return raw ? (JSON.parse(raw) as RateLimitRecord) : undefined;
},
async set(key, value) {
await redis.set(key, JSON.stringify(value), "PX", value.resetAt);
},
async delete(key) {
await redis.del(key);
},
};
app.use(
"*",
tenantRateLimit({
maxRequests: 1000,
windowMs: 60_000,
store: redisStore,
keyPrefix: "ratelimit:v1",
})
);
API Reference
tenantRateLimit(options): MiddlewareHandler.interface TenantRateLimitOptions—maxRequests,windowMs,store?,keyPrefix?,clock?.interface RateLimitStore—get,set,delete.interface RateLimitRecord—{ count, resetAt }.class MemoryRateLimitStore— public constructor(maxEntries = 10_000), exposes.sizefor assertions.
Implementation Notes
MemoryRateLimitStore.set deletes-and-reinserts on overwrite to keep
LRU ordering consistent — this guarantees the same key never inflates
the apparent "newness" through repeated updates. The 429 response is
returned (not thrown) so the rest of the middleware chain can short
circuit cleanly; the TenantError(TENANT_RATE_LIMITED) instance is
constructed for the message-formatting path and contributes the code
to the conformance gate's "every code raised" check.