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

2.0 KiB

Host Reserved-Prefix Contract

Package: @suaveplan/api-host Status: PROPOSED Type: capability-extension

Why

Stage 6.3 (api-auth-better-auth) needs to refuse mounting under host-reserved path prefixes (/_host, /internal, /graphql, etc.) and surface AUTH_BA_ROUTE_PREFIX_COLLISION. There is no canonical list — api-host uses /internal/livez and /internal/readyz internally, and Stage 7 will add /graphql, but no contract exists for a downstream adapter to query "is this prefix reserved?". Hard-coding the list inside each adapter would drift the moment any host route changes.

This change adds the reserved-prefix contract to @suaveplan/api-host:

  • HOST_RESERVED_PREFIXES — frozen ReadonlyArray<string> of all prefixes the host (and its first-party engines) reserve.
  • isReservedPrefix(prefix: string): boolean — predicate consumers call before mounting.
  • assertPrefixAvailable(prefix, codeFn) — throws via the consumer-supplied code when reserved.
  • ./reserved-prefix subpath export so adapters import without pulling the full host bundle.

What changes

  • New feature folder packages/api-stack/api/api-host/src/reserved-prefix/ with reserved-prefix.ts, reserved-prefix.types.ts, reserved-prefix.md, reserved-prefix.test.ts, index.ts.
  • package.json gains ./reserved-prefix subpath export.
  • Capability spec gains the ADDED requirements below.
  • No breaking changes; pure addition.

Reserved list (initial)

/_host, /internal, /admin (when admin-api mounted), /graphql (when GraphQL engine mounted), /ws (when WS engine mounted), /sse (when SSE engine mounted). Engine-conditional entries are returned only when the engine slot is filled in the registry; the static list returned by HOST_RESERVED_PREFIXES is the always-reserved subset.

Out of scope

  • Dynamic engine-conditional resolution beyond the static list. A follow-up (api-host-reserved-prefix-dynamic) will land when Stage 7 ships GraphQL/WS/SSE engines.