Files
skills/docs/api-stack/api/api-rate-limit
2026-05-29 08:20:56 +01:00
..
2026-05-29 08:20:56 +01:00
2026-05-29 08:20:56 +01:00

@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.

Documentation tree

  • error-codes — Defines the stable RATELIMIT.* error-code namespace for the @suaveplan/api-rate-limit package and the `RateLimitErr…
    • error-codes — Defines the stable RATELIMIT.* error-code namespace for the @suaveplan/api-rate-limit package and the `RateLimitErr…
  • instruments — Owns the rate-limit OTel instrument registry. Today the package emits exactly one counter — `api.rate_limit.exceeded_to…
    • instruments — Owns the rate-limit OTel instrument registry. Today the package emits exactly one counter — `api.rate_limit.exceeded_to…
  • middleware — The package's centerpiece. createRateLimitMiddleware builds a per-request gate around the token-bucket store; the gat…
    • middleware — The package's centerpiece. createRateLimitMiddleware builds a per-request gate around the token-bucket store; the gat…
  • selector — Resolves the rate-limit bucket key for a per-request frame, given the route-declared (or host-defaulted) `RateLimitKeyS…
    • selector — Resolves the rate-limit bucket key for a per-request frame, given the route-declared (or host-defaulted) `RateLimitKeyS…
  • memory-store — Provides the default in-memory implementation of RateLimitStoreLike. Backed by TokenBucket from `@suaveplan/rate-li…
    • memory-store — Provides the default in-memory implementation of RateLimitStoreLike. Backed by TokenBucket from `@suaveplan/rate-li…
  • store-like — Declares the RateLimitStoreLike interface — the storage abstraction behind the per-route token-bucket middleware. Kee…
    • store-like — Declares the RateLimitStoreLike interface — the storage abstraction behind the per-route token-bucket middleware. Kee…

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

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

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).