Files
2026-05-27 23:29:53 +01:00

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

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.