Initial Commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# `resolver` — `createAnonymousResolver`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Factory for the trivial `SessionResolver` that always returns `null`. This is
|
||||
the canonical tail-of-chain resolver every public-API host configures: it
|
||||
makes the "no auth" choice explicit in `host.json` and lets the rest of the
|
||||
host (RBAC gate, error envelope, OpenAPI emission) operate uniformly because
|
||||
`principal` is `null` not `undefined`. The factory shape exists — rather than
|
||||
exposing a constant — so future option flags (e.g. `logAnonymousAccess`) can
|
||||
be threaded in without a breaking surface change.
|
||||
|
||||
## Features
|
||||
|
||||
- Always resolves `null` — no header inspection, no network I/O, no state.
|
||||
- Idempotent `dispose()` no-op (resolver holds no resources).
|
||||
- Strict zod options schema rejects non-object input and unknown keys with
|
||||
`AUTH_ANON.INVALID_OPTIONS`.
|
||||
- Returned type is `SessionResolver` from `@suaveplan/api-auth/resolver` so
|
||||
it composes directly into `SessionResolverChain`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createAnonymousResolver } from "@suaveplan/api-auth-anonymous/resolver";
|
||||
|
||||
const resolver = createAnonymousResolver();
|
||||
// Inside a request pipeline:
|
||||
const principal = await resolver.resolve(request); // → null
|
||||
await resolver.dispose(); // safe to call again
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose as the tail of a resolver chain so a JWT-typed resolver runs first
|
||||
and the anonymous resolver supplies the `null` short-circuit for unrecognised
|
||||
requests on public routes:
|
||||
|
||||
```ts
|
||||
import { SessionResolverChain } from "@suaveplan/api-auth";
|
||||
import { createAnonymousResolver } from "@suaveplan/api-auth-anonymous/resolver";
|
||||
|
||||
const chain = new SessionResolverChain([
|
||||
jwtResolver, // recognises Bearer tokens, else returns null
|
||||
apiKeyResolver, // recognises x-api-key, else returns null
|
||||
createAnonymousResolver(), // tail: always null
|
||||
]);
|
||||
|
||||
const principal = await chain.resolve(request);
|
||||
// principal is now `Principal | null`. The host applies its `publicRoutes`
|
||||
// decision: a non-public route 401s; a public route proceeds with null.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
|------------------------------|----------|-------------------------------------------------------|
|
||||
| `createAnonymousResolver` | function | `(opts?: AnonymousResolverOptions) => SessionResolver`. |
|
||||
| `AnonymousResolverOptions` | type | Reserved-for-future-flags options bag. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The resolver returns `Promise<null>` literally rather than the wider
|
||||
`Promise<Principal | null>` so type narrowing in tests can use
|
||||
`expectTypeOf<...>().toEqualTypeOf<Promise<null>>()`. The chain widens it
|
||||
back to `Principal | null` at the boundary.
|
||||
- `dispose()` is a `Promise<void>` not just any promise — the conformance
|
||||
harness asserts the resolved value is `undefined` so this is load-bearing.
|
||||
- The factory is the public surface (not a const) because future flags must
|
||||
be addable without a breaking change to call sites. Even today, passing
|
||||
`{}` is the canonical call.
|
||||
- No OTel instrumentation is emitted from this resolver. Anonymous-access
|
||||
observability lives at the host layer (per-request span tag) — adding
|
||||
counters here would force a foundation-tier package to depend on
|
||||
`@suaveplan/opentelemetry`, which is undesirable for a hot path.
|
||||
Reference in New Issue
Block a user