Files
suaveplan/packages/api-stack/api/api-auth-provider/src/route-surface/route-surface.md
2026-05-27 23:29:53 +01:00

3.3 KiB

route-surface — canonical auth-provider route surface

(no submodules)

Purpose

route-surface declares the closed set of paths an auth provider OWNS relative to its pathPrefix. The set is the union of the routes any realistic concrete provider exposes — register, login, logout, refresh, two OAuth-flow endpoints, email verification, and the forgot/reset-password pair. Providers MAY implement a subset; the canonical list is the bar the collision detector measures against and the manifest the OpenAPI emitter projects when stitching a unified document at build time.

Features

  • PROVIDER_ROUTE_SURFACE — frozen array of ProviderRouteEntry records, every entry carrying path, method, stable name, and OpenAPI summary.
  • ProviderRouteName literal union — exhaustive narrowing in switch blocks.
  • lookupProviderRoute(name) — name → entry lookup.
  • resolveProviderRoutePath(prefix, entry) — mount under a given prefix (handles "/" as a no-op).
  • listProviderRouteNames() — declaration-order name iteration; used by the conformance harness to verify every canonical name has a scenario.

Usage: Basic

import {
    PROVIDER_ROUTE_SURFACE,
    resolveProviderRoutePath,
} from "@suaveplan/api-auth-provider/route-surface";

for (const entry of PROVIDER_ROUTE_SURFACE) {
    console.log(entry.method, resolveProviderRoutePath("/auth", entry));
}

Usage: Advanced

The OpenAPI emitter walks the surface to generate the global paths block for the unified document, projecting summary directly into the OpenAPI operation and using name as operationId after kebab → camelCase conversion.

import {
    PROVIDER_ROUTE_SURFACE,
    resolveProviderRoutePath,
} from "@suaveplan/api-auth-provider/route-surface";

const openapiPaths: Record<string, unknown> = {};
for (const entry of PROVIDER_ROUTE_SURFACE) {
    const path = resolveProviderRoutePath(provider.pathPrefix, entry);
    openapiPaths[path] = {
        [entry.method.toLowerCase()]: {
            operationId: entry.name.replace(/[-.]/g, "_"),
            summary: entry.summary,
            tags: ["auth"],
        },
    };
}

API Reference

  • interface ProviderRouteEntry { path; method; name; summary }
  • type ProviderRouteName — closed union of 9 names.
  • const PROVIDER_ROUTE_SURFACE: ReadonlyArray<ProviderRouteEntry>
  • function lookupProviderRoute(name: ProviderRouteName): ProviderRouteEntry | undefined
  • function resolveProviderRoutePath(prefix: string, entry: ProviderRouteEntry): string
  • function listProviderRouteNames(): ReadonlyArray<ProviderRouteName>

Implementation Notes

The surface is frozen at module-load with two layers of Object.freeze — once around the outer array and once around each entry — to keep downstream hosts from mutating the table at runtime. resolveProviderRoutePath treats "/" as the empty-prefix case so a deployment that mounts the provider at the host root produces "/login" rather than "//login". Adding a new canonical route requires an OpenSpec change because the conformance harness and the OpenAPI projector both treat the surface as authoritative; touching either side without the other is the bug class this list-as-data design guards against.