3.1 KiB
selector
(no submodules)
Purpose
Resolves the rate-limit bucket key for a per-request frame, given the
route-declared (or host-defaulted) RateLimitKeySelector. The
selector kind controls how the middleware fans the per-route budget
across distinct callers — "tenant" shares the bucket across every
principal in a tenant, "principal" isolates by authenticated user,
"ip" falls back to the network identity, and the "custom" form
gives the route author a free-form projection.
Features
- Discriminated union
RateLimitKeySelector = "tenant" | "principal" | "ip" | { kind: "custom"; select }. - Default selection (
selector === undefined) prefers"principal"when authenticated, else"ip"— matching design doc §6 step 4a. - Strict-mode fallback to the literal
"anon"when the chosen kind's source isnull(no tenant, no principal, no IP). The middleware never crashes; the limit just shares a single bucket across every truly-anonymous caller. - Returned
ResolvedKeycarries both the bucket key string AND thekindlabel so the OTel counter can attach a stablekey_kindattribute without re-parsing the key.
Usage: Basic
import { resolveRateLimitKey } from "@suaveplan/api-rate-limit/selector";
const { key, kind } = resolveRateLimitKey(
{ tenantId: "t1", principal: { id: "alice" }, ip: "10.0.0.1", request: new Request("https://h/") },
"principal"
);
// → { key: "principal:alice", kind: "principal" }
Usage: Advanced
A custom selector that buckets per (tenant, route-action-token) so
two different mutations on the same route share fan-in but distinct
buckets:
import { resolveRateLimitKey, type RateLimitKeySelector } from "@suaveplan/api-rate-limit/selector";
const custom: RateLimitKeySelector = {
kind: "custom",
select: (ctx) => {
const u = new URL(ctx.request.url);
const action = u.searchParams.get("action") ?? "default";
return `${ctx.tenantId ?? "anon"}:${action}`;
},
};
const r = resolveRateLimitKey(
{ tenantId: "t1", principal: null, ip: "10.0.0.1", request: new Request("https://h/q?action=write") },
custom
);
// → { key: "t1:write", kind: "custom" }
API Reference
type RateLimitKeySelector = "tenant" | "principal" | "ip" | { readonly kind: "custom"; readonly select: (ctx: RateLimitContext) => string }interface RateLimitContext { tenantId: string | null; principal: { id: string } | null; ip: string | null; request: Request }interface ResolvedKey { readonly key: string; readonly kind: "tenant" | "principal" | "ip" | "custom" }function resolveRateLimitKey(ctx, selector?): ResolvedKey
Implementation Notes
The function is pure — it does not consult environment, the clock, or
the store. That makes it trivial to unit-test the default selector
chain against a structural RateLimitContext literal without
spinning up a full request pipeline. The custom selector kind is
deliberately the only variant that takes a callback; everything else
is data so the selector is plain JSON-serialisable for
host.json-level overrides.