# `resolver` _(no submodules)_ ## Purpose Implements The tenant resolver factory `createJwtTenantResolver`. The resolver reads the tenant identifier from an already-resolved `Principal` attached by an earlier `api-auth-*` chain step and projects it into a `TenantContext`. When the configured claim is present the resolver returns that context (the chain short-circuits); when absent it returns `null` so a subsequent header / subdomain resolver can take over. The resolver is intentionally trivial — it does no JWT verification of its own (that is `api-auth-jwt`'s job) and no network I/O. Its value is making the data-flow explicit: the host wires `auth → tenant-resolver-jwt → next` and the conformance suite gets to test the empty/missing-tenant edge cases uniformly without coupling the auth and tenant contracts. ## Features - `createJwtTenantResolver(opts?)` factory returning a `TenantResolver` conforming to `@suaveplan/api-types/tenant-resolver`. - Dependency-injected `getPrincipal` extractor: defaults to reading `(request as { principal }).principal`, override for host wiring backed by a `WeakMap` or `AsyncLocalStorage`. - Configurable `claimKey`: defaults to `"tenantId"` (reads `Principal.tenantId`); any other key reads `Principal.claims[key]` and brand-casts the string to `TenantId`. - Strict-mode `requirePrincipal`: when `true`, a missing principal or a missing claim throws `TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID` rather than returning `null`. - Pure read-path: no module state, no async beyond the `Promise<…>` shape required by the contract. ## Usage: Basic ```ts import { createJwtTenantResolver } from "@suaveplan/api-tenant-resolver-jwt/resolver"; const resolver = createJwtTenantResolver(); const ctx = await resolver.resolve(request); if (ctx) { // → { id: "acme" as TenantId, slug: "acme" } } ``` ## Usage: Advanced ```ts import type { Principal } from "@suaveplan/api-types/principal"; import { createJwtTenantResolver } from "@suaveplan/api-tenant-resolver-jwt/resolver"; const principals = new WeakMap(); const resolver = createJwtTenantResolver({ id: "jwt-primary", claimKey: "tid", requirePrincipal: true, getPrincipal: (req) => principals.get(req) ?? null, }); // host wiring (api-auth-jwt would do this in production): principals.set(request, { id: "u" as never, scopes: [], claims: { tid: "acme" } }); const ctx = await resolver.resolve(request); // → { id: "acme" as TenantId, slug: "acme" } — short-circuits the chain ``` ## API Reference - `type PrincipalExtractor = (request: Request) => Principal | null | undefined`. - `interface CreateJwtTenantResolverOptions extends JwtTenantResolverOptions { getPrincipal?: PrincipalExtractor }`. - `function createJwtTenantResolver(options?): TenantResolver` — returns `{ id, resolve(request) }`. `resolve` returns `Promise`; throws `JwtTenantResolverError(PRINCIPAL_MISSING_TENANT_ID)` only when `requirePrincipal: true` and the principal / claim is absent. ## Implementation Notes The resolver brand-casts the claim value to `TenantId` without further validation: the principal has already passed an `api-auth-*` adapter that owns the verification of the JWT signature and the trust boundary. Re-validating here would duplicate work the host already performed. Empty-string claim values are treated as absent so a misconfigured signer can never accidentally route all traffic into a tenant whose slug is the empty string. The default `PrincipalExtractor` reads `request.principal`, which works for the simple host wiring (mutate the request once after auth resolves) but falls down for environments where the `Request` is frozen (Workers, certain edge runtimes). In those cases pass an explicit `getPrincipal` backed by a `WeakMap` populated by the auth middleware.