# 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 is `null` (no tenant, no principal, no IP). The middleware never crashes; the limit just shares a single bucket across every truly-anonymous caller. - Returned `ResolvedKey` carries both the bucket key string AND the `kind` label so the OTel counter can attach a stable `key_kind` attribute without re-parsing the key. ## Usage: Basic ```ts 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: ```ts 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.