Files
suaveplan/packages/api-stack/api/api-tenant-resolver-subdomain/src/options/options.md
2026-05-27 23:29:53 +01:00

3.1 KiB

options — Zod-validated resolver options

(no submodules)

Purpose

Defines and validates the public option bundle accepted by createSubdomainTenantResolver. Every option is bounded by a Zod schema at construction time so an operator that misspells a key, supplies an invalid CIDR, or passes a negative tenantLabelIndex discovers the mistake immediately rather than at the first inbound request. The resolved bundle replaces every ?? ladder so the resolver body stays small and the hot-path is allocation-free.

The shape mirrors @suaveplan/api-tenant-resolver-header / -jwt (id + strict + validator + registry) plus the subdomain-specific fields (baseDomain, tenantLabelIndex, trustForwardedHost, trustedProxyCidrs) and a getTransport extractor so the host can inject framework-specific request metadata.

Features

  • Strict Zod schema — extra keys fail validation.
  • CIDR pre-parsing at construction time so the per-request hot-path is byte-array prefix comparison only.
  • Stable INVALID_OPTIONS code on every failure (Zod, CIDR parse, missing option object).
  • LabelValidator and TenantRegistryLookup opt-in extension points.

Usage: Basic

import { parseSubdomainTenantResolverOptions } from "@suaveplan/api-tenant-resolver-subdomain/options";

const resolved = parseSubdomainTenantResolverOptions({
    baseDomain: "api.example.com",
});
// → { id: "subdomain", baseDomain: "api.example.com",
//     tenantLabelIndex: 0, strict: false, ... }

Usage: Advanced

import type { TenantId } from "@suaveplan/api-types/tenant";
import { parseSubdomainTenantResolverOptions } from "@suaveplan/api-tenant-resolver-subdomain/options";

const registry = new Map<string, TenantId>([
    ["acme", "t_acme" as TenantId],
]);

const resolved = parseSubdomainTenantResolverOptions({
    id: "primary",
    baseDomain: "api.example.com",
    tenantLabelIndex: 0,
    strict: true,
    trustForwardedHost: true,
    trustedProxyCidrs: ["10.0.0.0/8", "2001:db8::/32"],
    registry: (slug) => registry.get(slug) ?? null,
});

API Reference

  • SubdomainTenantResolverOptions — public input bundle.
  • ResolvedSubdomainTenantResolverOptions — internal, every default applied, CIDRs pre-parsed.
  • parseSubdomainTenantResolverOptions(input): Resolved....
  • TenantRegistryLookup — slug → TenantId | null (sync or async).
  • RequestTransport — { host, forwardedHost, remoteAddress }.
  • TransportExtractor — (request: Request) => RequestTransport.

Implementation Notes

The schema is .strict() so a typo (requirePrincipal instead of requireTenant) is a hard error at construction. validate and registry are stripped before the Zod parse — z.function() is deprecated and unnecessary here because runtime function calls already fail loudly if the wrong shape is passed. trustedProxyCidrs is re-parsed through parseCidr so every CIDR is normalised to a ParsedCidr once; this is the only allocation per resolver construction (per-request matching uses the cached array).