Files
suaveplan/packages/api-stack/api/api-tenant/src/rate-limit/rate-limit.md
2026-05-27 23:29:53 +01:00

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 RateLimitStore interface (get, set, delete). Adapters for Redis or SQL plug in via the same shape.
  • Default MemoryRateLimitStore — bounded by maxEntries, LRU eviction, in-process only.
  • Sliding-fixed window — counter resets at now + windowMs; the next request after resetAt starts a fresh window.
  • 429 response carries Retry-After, X-RateLimit-{Limit,Remaining, Reset}, and a JSON body with code: "TENANT.RATE_LIMITED".
  • Injectable clock — FakeClock interop, 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 .size for 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.