first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
# reserved-prefix
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The reserved-prefix contract publishes the canonical list of URL path prefixes that `@suaveplan/api-host` (and every first-party engine that ships under the host) considers reserved. Without it, downstream adapters such as `@suaveplan/api-auth-better-auth`, `@suaveplan/api-plugin-rbac`, or any user-authored plugin would hard-code a private snapshot of host-internal routes and silently drift the moment a new host module adds an endpoint.
This module is the single source of truth: it exposes the frozen `HOST_RESERVED_PREFIXES` array, the `isReservedPrefix` predicate, the `normalisePrefix` canonicalisation helper, and the `assertPrefixAvailable` assertion that throws a consumer-supplied error on collision. Adapters consult it before mounting their own routes and refuse to install — surfacing their own code namespace (`AUTH_BA_ROUTE_PREFIX_COLLISION`, `RBAC_ROUTE_PREFIX_COLLISION`, …) — when an operator-configured mount path collides with a host-reserved route.
## Features
- Frozen `ReadonlyArray<string>` of always-reserved prefixes (`/_host`, `/internal`).
- Case-insensitive, trailing-slash-tolerant, leading-slash-tolerant prefix matching.
- Exact-match AND child-path detection (`/internal/livez` is also reserved).
- `assertPrefixAvailable(prefix, factory)` throws the factory-supplied error lazily — the factory is only invoked on collision so adapters can defer expensive context construction to the failure path.
- Branded `ReservedPrefix` type tagging values that have been canonicalised through `normalisePrefix`, so type-aware pipelines can prove their input is in the canonical form without re-normalising at every hop.
- Published as a `./reserved-prefix` subpath export — adapters import only the predicate surface without pulling in the full host bundle.
## Usage: Basic
```ts
import {
HOST_RESERVED_PREFIXES,
isReservedPrefix,
} from "@suaveplan/api-host/reserved-prefix";
if (isReservedPrefix("/internal/foo")) {
throw new Error("cannot mount under a host-reserved prefix");
}
console.log(HOST_RESERVED_PREFIXES); // ["/_host", "/internal"]
```
## Usage: Advanced
Adapter-side, the typical wiring uses `assertPrefixAvailable` with the adapter's own error code namespace:
```ts
import { assertPrefixAvailable } from "@suaveplan/api-host/reserved-prefix";
import { SuaveplanError } from "@suaveplan/error";
import { AUTH_BA_CODES } from "@suaveplan/api-auth-better-auth/codes";
export function mountBetterAuth(prefix: string): void {
assertPrefixAvailable(prefix, (collision) =>
new SuaveplanError(AUTH_BA_CODES.ROUTE_PREFIX_COLLISION, {
messageKey: "error.auth_ba.route_prefix_collision",
context: { requested: prefix, collision },
})
);
// …safe to register routes under `prefix` now.
}
```
The `normalisePrefix` helper is also exported so adapters that store an operator-supplied mount prefix at configuration time can canonicalise it once and rely on the form for subsequent comparisons.
## API Reference
- `HOST_RESERVED_PREFIXES: ReadonlyArray<string>` — frozen, deduped, normalised list of always-reserved prefixes.
- `isReservedPrefix(prefix: string): boolean` — `true` when `prefix` (after normalisation) equals or is a child path of any reserved entry.
- `normalisePrefix(prefix: string): string` — lowercases, prepends `/`, collapses trailing slashes; empty input → `/`.
- `assertPrefixAvailable(prefix, codeFactory)` — invokes `codeFactory(normalisedCollision)` and throws when reserved; no-op otherwise.
- `brandPrefix(prefix: string): ReservedPrefix` — normalises and returns the value retyped as the `ReservedPrefix` brand.
- `type ReservedPrefix` — branded string carrying the proof of canonicalisation.
- `type ReservedPrefixErrorFactory` — `(collision: string) => Error`.
## Implementation Notes
The list is initialised by feeding `["/_host", "/internal"]` through `normalisePrefix`, then deduping via `Set`, then `Object.freeze` so the returned array refuses mutation at runtime (strict mode throws `TypeError`). Predicate matching is O(n) over the (currently 2-entry) reserved list; for child-path matching the implementation uses `startsWith(`${reserved}/`)` rather than `startsWith(reserved)` to avoid the `/internal` reserves `/internals` false positive.
Engine-conditional entries (`/admin`, `/graphql`, `/ws`, `/sse`) are intentionally NOT in the static list. They will land via a follow-up `api-host-reserved-prefix-dynamic` capability that inspects the active host registry — keeping the static contract immutable and runtime-deterministic. The branded `ReservedPrefix` type uses a `unique symbol` field; the brand never materialises at runtime, so `JSON.stringify` of a branded value is identical to its underlying string.