Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# resolver
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `resolver` module ships the six built-in `TenantResolver`
implementations consumed by `createTenantMiddleware` plus a
`ChainedTenantResolver` that composes them in priority order. Each
resolver reads one piece of the inbound `hono` `Context` (header,
hostname, path, JWT claim) and delegates the actual data fetch to a
caller-supplied `lookupFn` — the package never owns a tenant store,
which keeps the surface usable against in-memory maps, SurrealDB,
Postgres, or anything else.
Resolution returns `null` when no tenant matches. The middleware
treats `null` as "this request is tenant-less" and either fails-closed
or skips the status gate based on the `required` option.
## Features
- `HeaderTenantResolver(headerName, lookupFn)` — one HTTP header.
- `SubdomainTenantResolver(baseDomain, lookupFn)` — strips port and
the literal `www` label.
- `DomainTenantResolver(lookupFn)` — full hostname (minus port) as
the lookup key. Useful for custom-domain mappings.
- `PathTenantResolver(prefix, lookupFn)` — reads the slug immediately
after `prefix`.
- `JWTTenantResolver(claimName, lookupFn)` — reads `c.get("user")`
set by an upstream auth middleware and extracts the configured claim.
- `ChainedTenantResolver(...resolvers)` — invokes children in order,
short-circuits on the first non-null result.
- `TimeoutTenantResolver(inner, timeoutMs)` — raises
`TenantError(TENANT_LOOKUP_TIMEOUT)` when the wrapped resolver does
not settle within `timeoutMs` milliseconds.
- Factory functions (`headerResolver`, `subdomainResolver`, etc.)
for terse call sites; the class form is exposed for callers that
need to subclass.
## Usage: Basic
```ts
import { headerResolver } from "@suaveplan/api-tenant";
const resolver = headerResolver("x-tenant-id", async (id) => db.tenants.get(id));
```
## Usage: Advanced
Composing three strategies into a deterministic priority chain:
```ts
import {
chainResolvers,
headerResolver,
jwtResolver,
subdomainResolver,
} from "@suaveplan/api-tenant";
const lookupBySlug = (slug: string) => db.tenants.bySlug(slug);
const lookupById = (id: string) => db.tenants.byId(id);
const resolver = chainResolvers(
headerResolver("x-tenant-id", lookupById),
jwtResolver("tenantId", lookupById),
subdomainResolver("suaveplan.io", lookupBySlug)
);
```
## API Reference
- `interface TenantResolver { name; resolve(c) }`.
- `type TenantLookupFn = (key: string) => Promise<Tenant | null>`.
- `HeaderTenantResolver`, `SubdomainTenantResolver`,
`DomainTenantResolver`, `PathTenantResolver`, `JWTTenantResolver`,
`ChainedTenantResolver` — classes.
- `headerResolver`, `subdomainResolver`, `domainResolver`,
`pathResolver`, `jwtResolver`, `chainResolvers` — factory functions.
## Implementation Notes
The subdomain resolver intentionally treats `www` as "no subdomain";
the domain resolver treats it as the lookup key. Choose between them
based on whether `www.acme.com` should map to the same tenant as
`acme.com` (`subdomain` chained with a domain fallback) or to a
separate marketing surface (`domain` alone). The JWT resolver narrows
on `typeof tenantId === "string"` so spurious numeric / object claims
fall through to the next resolver in the chain rather than crashing
the lookup.