first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# `path-ownership` — single-provider registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`path-ownership` is the runtime registry the host consults to enforce the
|
||||
"exactly one auth provider per deployment" invariant from design §2.3. A
|
||||
provider claims its `pathPrefix` at install time; any subsequent registration
|
||||
with a different provider id throws `MULTIPLE_PROVIDERS_REGISTERED`. The
|
||||
registry is deliberately not a module-global singleton — each host instance
|
||||
constructs its own `PathOwnership`, which keeps multi-tenant test runners
|
||||
and hot-reload scenarios deterministic.
|
||||
|
||||
## Features
|
||||
|
||||
- `PathOwnership` class with `register`, `release`, `current`, `hasProvider`.
|
||||
- Injectable `clock` — defaults to `Date.now`, used for the `claimedAt`
|
||||
timestamp on the recorded claim.
|
||||
- Idempotent registration — re-registering the same instance, or a different
|
||||
instance with the same provider id, returns the existing claim.
|
||||
- Strict rejection of a different provider id with
|
||||
`AUTH_PROVIDER.MULTIPLE_PROVIDERS_REGISTERED`, including a structured
|
||||
`context` payload (`incomingProviderId`, `incumbentProviderId`,
|
||||
`prefix`) suitable for diagnostic logs.
|
||||
- `release()` drops the current claim — used by host shutdown and the
|
||||
blue-green hot-reload sequence to swap providers atomically.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { PathOwnership } from "@suaveplan/api-auth-provider/path-ownership";
|
||||
|
||||
const ownership = new PathOwnership();
|
||||
ownership.register(myProvider);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Tests inject a deterministic clock so the recorded `claimedAt` is stable
|
||||
across runs:
|
||||
|
||||
```ts
|
||||
import { PathOwnership } from "@suaveplan/api-auth-provider/path-ownership";
|
||||
|
||||
const ownership = new PathOwnership({ clock: () => 1_000_000 });
|
||||
const claim = ownership.register(myProvider);
|
||||
// claim.claimedAt === 1_000_000
|
||||
|
||||
// Hot-reload: release before swapping in the new provider.
|
||||
ownership.release();
|
||||
ownership.register(replacementProvider);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class PathOwnership` — runtime registry.
|
||||
- `constructor(options?: PathOwnershipOptions)`
|
||||
- `register(provider): PathOwnershipClaim`
|
||||
- `release(): void`
|
||||
- `current(): ProviderClaim | null`
|
||||
- `hasProvider(): boolean`
|
||||
- `interface PathOwnershipOptions { clock? }`
|
||||
- `type OwnershipClock = () => number`
|
||||
- `interface PathOwnershipClaim { providerId; prefix; claimedAt }`
|
||||
- `interface ProviderClaim<Provider> { provider; claim }`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The registry stores both the `AuthProviderAdapter` instance and a flat
|
||||
`PathOwnershipClaim` record. Two layers exist because consumers fall into
|
||||
two camps: hosts that need the live provider for lifecycle dispatch, and
|
||||
diagnostic tooling that only needs the metadata. Idempotency is decided by
|
||||
identity first, id second — so a host that constructs a fresh provider
|
||||
instance per hot-reload still cannot accidentally register two providers
|
||||
with the same id. The `MULTIPLE_PROVIDERS_REGISTERED` error carries enough
|
||||
context for operators to diagnose double-installation in a misconfigured
|
||||
multi-process deployment without consulting the host logs. The class uses a
|
||||
private field rather than a module-level variable so multiple registries can
|
||||
coexist in the same process — essential for runner-agnostic test suites and
|
||||
multi-tenant in-process deployments.
|
||||
Reference in New Issue
Block a user