# @suaveplan/api-tenant-resolver-jwt — tenant-resolver adapter for the Suaveplan API plugin system. Reads the already-resolved `Principal.tenantId` from the per-request context (attached by an earlier `api-auth-*` chain) and projects it into a `TenantContext`. When the configured claim is present the resolver short-circuits the chain; when absent it returns `null` so a header/subdomain resolver can take over. Configurable claim key (default `tenantId`) and strict-mode (`requirePrincipal: true`) that throws `TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID` instead of advancing. Stage-4 tenant resolver for the Suaveplan API plugin system. Reads the already-resolved `Principal.tenantId` (attached by an earlier `api-auth-*` chain step) and projects it into a `TenantContext` for the host's per-request pipeline. ```ts import { createJwtTenantResolver } from "@suaveplan/api-tenant-resolver-jwt"; const resolver = createJwtTenantResolver(); const ctx = await resolver.resolve(request); // → { id: "acme", slug: "acme" } | null ``` ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry for `@suaveplan/api-tenant-resolver-jwt`. Every public failure path in the resol… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry for `@suaveplan/api-tenant-resolver-jwt`. Every public failure path in the resol… - [options](./src/options/options.md) — Defines and validates the `JwtTenantResolverOptions` accepted by `createJwtTenantResolver`. The options shape is delibe… - [options](./src/options/options.md) — Defines and validates the `JwtTenantResolverOptions` accepted by `createJwtTenantResolver`. The options shape is delibe… - [resolver](./src/resolver/resolver.md) — Implements The tenant resolver factory `createJwtTenantResolver`. The resolver reads the tenant identifier from… - [resolver](./src/resolver/resolver.md) — Implements The tenant resolver factory `createJwtTenantResolver`. The resolver reads the tenant identifier from… ## Why a dedicated package The tenant-resolver chain (design doc §6, step 4) runs after the session resolver. When the session is a JWT carrying `tenant_id` / `tid`, the tenant resolver's job is trivial: read `principal.tenantId`. Collapsing that into a dedicated adapter — rather than encoding it inside `api-auth-jwt` — keeps the two contracts orthogonal and lets operators chain `tenant-resolver-jwt` with `api-tenant-resolver-header` and `api-tenant-resolver-subdomain` (Stage 8) for the paths a JWT alone doesn't cover. ## Chain interaction (forward reference) The host runs resolvers left-to-right; the first non-`null` result wins: ``` [ api-tenant-resolver-jwt, api-tenant-resolver-header, api-tenant-resolver-subdomain ] | | | reads principal reads X-Tenant-Id reads sub.domain (typical bearer) (admin / S2S calls) (public site routes) ``` When the JWT carries no tenant claim (or the request is unauthenticated on a public route), `tenant-resolver-jwt` returns `null` and the next resolver in the chain runs. With `requirePrincipal: true` the chain stops here and a 401-equivalent is surfaced via `TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID`. ## Options | Option | Default | Effect | | ------------------ | ------------- | ------------------------------------------------------------------ | | `id` | `"jwt"` | Resolver id used in chain logs and metric tags. | | `claimKey` | `"tenantId"` | Reads `Principal.tenantId` directly; any other key reads `claims`. | | `requirePrincipal` | `false` | Throw instead of returning `null` when claim is absent. | | `getPrincipal` | reads `req.principal` | Override how the principal is fetched from the request. | ## Error codes - `TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID` — strict-mode rejected. - `TENANT_RESOLVER_JWT.INVALID_OPTIONS` — construction-time Zod failure. ## Exports - `.` — top-level barrel re-exporting everything below. - `./resolver` — `createJwtTenantResolver`, `CreateJwtTenantResolverOptions`, `PrincipalExtractor`. - `./options` — `JwtTenantResolverOptions`, `parseJwtTenantResolverOptions`. - `./error-codes` — `JwtTenantResolverError`, `TENANT_RESOLVER_JWT_CODES`, `JwtTenantResolverErrorCode`.