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,99 @@
# `factory/create-static-principal-resolver` — fixed-Principal resolver
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Constructs a `SessionResolver` from `@suaveplan/api-auth` that returns the
configured `Principal` verbatim on every request — no token decoding, no
JWKS, no I/O. This is the workhorse adapter for plugin integration tests,
the `api-plugin-example` fixture, and any environment where the goal is to
exercise the downstream pipeline (RBAC gate, tenant resolver chain reading
`principal.tenantId`, tenant-scoped DB) with a deterministic identity carried
in config.
The factory consumes options previously validated by the `options` module so
construction is total: every misuse maps to either
`AUTH_STATIC_PRINCIPAL.INVALID_OPTIONS` or `AUTH_STATIC_PRINCIPAL.INVALID_PRINCIPAL`
and surfaces synchronously at construction time. The resolver itself never
throws at request time.
## Features
- Defensive clone on every `resolve()` — callers cannot mutate one returned
Principal and observe the change on subsequent calls.
- `applyTo(req)` predicate for arbitrary "return null for this request"
rules.
- `nullForPaths` suffix-glob list (`/webhooks/*`) and exact-match patterns
for the most common public-route test setup.
- Idempotent no-op `dispose()` matching the `SessionResolver` contract.
- Returned objects are frozen (top-level + `scopes` + `claims`) to make
attempted mutations fail loudly in strict mode.
## Usage: Basic
```ts
import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver";
const resolver = createStaticPrincipalResolver({
principal: {
id: "u1",
scopes: ["billing:write"],
tenantId: "acme",
claims: { sub: "u1" },
},
});
const principal = await resolver.resolve(
new Request("https://example.test/api/billing/invoices")
);
// principal.id === "u1"
```
## Usage: Advanced
Combine `applyTo` and `nullForPaths` to model a mixed-route test surface:
```ts
import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver";
const resolver = createStaticPrincipalResolver({
principal: {
id: "u1",
scopes: ["billing:write", "billing:read"],
tenantId: "acme",
claims: { sub: "u1" },
},
nullForPaths: ["/webhooks/*", "/healthz"],
applyTo: (req) => !req.headers.has("x-skip-auth"),
});
// /healthz and /webhooks/stripe resolve to null.
// Requests with the `x-skip-auth` header resolve to null.
// Everything else returns the configured Principal.
```
## API Reference
| Symbol | Kind | Description |
| ----------------------------------- | -------- | ------------------------------------------------------------------ |
| `createStaticPrincipalResolver(o)` | function | Returns a `SessionResolver` carrying `o.principal` on every match. |
## Implementation Notes
The clone strategy is: deep-freeze a single template at construction, then
clone-from-template per `resolve()`. The per-call clone is unavoidable —
otherwise two callers each freezing the returned object would freeze each
other's state. The per-call clone copies the top level plus `scopes` (array
spread) and `claims` (object spread); deeper structures inside `claims` are
not cloned. This matches the `Principal` contract: `claims` values are
`unknown`, so the package can't responsibly deep-clone them. Callers passing
mutable nested claims values are responsible for not mutating them in-place.
`pathMatches` accepts only exact-match and a single suffix glob (`/foo/*`)
on purpose — richer matching belongs in `applyTo`. The trailing slash is
preserved when stripping the `*` so `/foo/*` matches `/foo/x` but not
`/foox`. The pattern dialect is documented on the spec and exercised by the
test file.