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:
@@ -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.
|
||||
Reference in New Issue
Block a user