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,74 @@
|
||||
# @suaveplan/api-router-in-memory — in-memory router adapter for the @suaveplan/api plugin system. Implements both the abstract Router contract from @suaveplan/api-router AND the PipelineRouter contract from @suaveplan/api-router on a single InMemoryRouter class. Production-grade in-memory adapter — keeps state in process, dispatches without binding a socket. Passes runRouterConformance + runPipelineRouterConformance.
|
||||
|
||||
Stage-5 in-memory router adapter for `@suaveplan/api`. Implements both the abstract `Router` contract from `@suaveplan/api-router` AND the `PipelineRouter` contract from `@suaveplan/api-router` on a single `InMemoryRouter` class. Production-grade in-memory adapter — not a test double.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/adapter.md) — `InMemoryRouter` is the Stage-5 in-memory router adapter for `@suaveplan/api`. It implements both…
|
||||
- [adapter](./src/adapter/adapter.md) — `InMemoryRouter` is the Stage-5 in-memory router adapter for `@suaveplan/api`. It implements both…
|
||||
- [blue-green-slot](./src/blue-green-slot/blue-green-slot.md) — `InMemoryBlueGreenSlot` is the `BlueGreenSlot` implementation paired with `InMemoryRouter`. It is a thin extension of `…
|
||||
- [blue-green-slot](./src/blue-green-slot/blue-green-slot.md) — `InMemoryBlueGreenSlot` is the `BlueGreenSlot` implementation paired with `InMemoryRouter`. It is a thin extension of `…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `ROUTER_IN_MEMORY.*` error-code namespace used by every production path in `@suaveplan/api-router-in…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `ROUTER_IN_MEMORY.*` error-code namespace used by every production path in `@suaveplan/api-router-in…
|
||||
- [pipeline](./src/pipeline/pipeline.md) — `PipelineRouteTable` is the in-memory metadata store that backs `InMemoryRouter`'s pipeline facet. It stores per-route…
|
||||
- [pipeline](./src/pipeline/pipeline.md) — `PipelineRouteTable` is the in-memory metadata store that backs `InMemoryRouter`'s pipeline facet. It stores per-route…
|
||||
- [testing](./src/testing/testing.md) — `createInMemoryRouterHarness()` is the canonical "spin up an in-memory router for a test" entry point. It bundles a fre…
|
||||
- [testing](./src/testing/testing.md) — `createInMemoryRouterHarness()` is the canonical "spin up an in-memory router for a test" entry point. It bundles a fre…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## What it ships
|
||||
|
||||
- `InMemoryRouter` / `createInMemoryRouter()` — concrete `Router` adapter that subclasses the abstract base AND exposes the pipeline facet (`registerPipelineRoute`, `matchPipeline`, `listPipelineRoutes`, `unregisterPipelineRoute`, `unregisterPipelinePlugin`).
|
||||
- `InMemoryBlueGreenSlot` / `createInMemoryBlueGreenSlot` — hot-reload primitive paired with the router. Thin extension of `@suaveplan/api-router`'s reference `InMemoryBlueGreenSlot`.
|
||||
- `PipelineRouteTable` — the in-memory metadata store backing the pipeline facet, exported for advanced use cases that need to share a table across routers.
|
||||
- `createInMemoryRouterHarness()` — downstream test helper that bundles a router + slot factory pair, replacing the hand-rolled router shims that have grown across the foundation + plugin tests.
|
||||
- `ROUTER_IN_MEMORY_CODES` / `RouterInMemoryError` — stable error-code registry (`ROUTER_IN_MEMORY.ROUTE_COLLISION`, `ROUTER_IN_MEMORY.INVALID_PREFIX`).
|
||||
|
||||
## Conformance
|
||||
|
||||
The package passes BOTH foundation conformance suites:
|
||||
|
||||
- `runRouterConformance` from `@suaveplan/api-router/testing` — see `tests/conformance/api-router.test.ts`.
|
||||
- `runPipelineRouterConformance` from `@suaveplan/api-router/testing/pipeline-router-conformance` — see `tests/conformance/pipeline-router.test.ts`.
|
||||
|
||||
Adding new behaviours requires re-running both harnesses.
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/adapter/adapter.md`](./src/adapter/adapter.md) — `InMemoryRouter` + factory.
|
||||
- [`src/blue-green-slot/blue-green-slot.md`](./src/blue-green-slot/blue-green-slot.md) — hot-reload primitive.
|
||||
- [`src/pipeline/pipeline.md`](./src/pipeline/pipeline.md) — `PipelineRouteTable` + types.
|
||||
- [`src/testing/testing.md`](./src/testing/testing.md) — `createInMemoryRouterHarness` for downstream tests.
|
||||
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — `ROUTER_IN_MEMORY.*` registry.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import {
|
||||
createInMemoryRouter,
|
||||
InMemoryBlueGreenSlot,
|
||||
} from "@suaveplan/api-router-in-memory";
|
||||
|
||||
const router = createInMemoryRouter({ plugin: "billing" });
|
||||
router.get("/invoices/:id", () => null);
|
||||
|
||||
router.registerPipelineRoute({
|
||||
plugin: "billing",
|
||||
method: "POST",
|
||||
path: "/invoices",
|
||||
handler: async () => ({ ok: true }),
|
||||
});
|
||||
|
||||
const slot = new InMemoryBlueGreenSlot("billing", () => "v1");
|
||||
slot.setShadow(() => "v2");
|
||||
const draining = slot.flip();
|
||||
await slot.dispose(draining.slotId, { timeoutMs: 100 });
|
||||
```
|
||||
|
||||
## When to use
|
||||
|
||||
- Foundation tests that need a real `Router`/`PipelineRouter` without a Hono peer.
|
||||
- Plugin tests that previously hand-rolled their own router shim (`api-router-hono` carries the Hono peer-dep cost).
|
||||
- The default `router: "in-memory"` slot in the Stage-5 `@suaveplan/api-host/testing` adapter table.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# `adapter` — `InMemoryRouter`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`InMemoryRouter` is The in-memory router adapter for `@suaveplan/api`. It implements both the abstract `Router` contract from `@suaveplan/api-router` (verb registration, collision detection, mount/prefix rewriting, param parsing) AND the `PipelineRouter` contract from `@suaveplan/api-router` (route metadata sidecar with plugin ownership, status-map propagation, and hot-reload teardown).
|
||||
|
||||
Designed as a real, production-grade adapter that happens to keep its state in process memory — not a test double. The class passes both `runRouterConformance` and `runPipelineRouterConformance` end-to-end.
|
||||
|
||||
## Features
|
||||
|
||||
- Single concrete class satisfying two contracts (`Router` + `PipelineRouter`).
|
||||
- Zero network I/O — `match()`/`matchPipeline()` resolve from in-memory maps.
|
||||
- Plugin-isolation invariant: cross-plugin collisions throw `ROUTER_IN_MEMORY.ROUTE_COLLISION`; same-plugin re-registration overwrites (the hot-reload path).
|
||||
- `unregisterPipelinePlugin(plugin)` clears every entry owned by a plugin in one call — wired by the host's reload driver.
|
||||
- Compatible with `@suaveplan/api-router-in-memory/blue-green-slot`'s `InMemoryBlueGreenSlot` for the same hot-reload primitive Stage-4 ships.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createInMemoryRouter } from "@suaveplan/api-router-in-memory/adapter";
|
||||
|
||||
const router = createInMemoryRouter({ plugin: "billing" });
|
||||
router.get("/invoices/:id", () => null);
|
||||
|
||||
const match = router.match("GET", "/invoices/i_42");
|
||||
// match.params.id === "i_42"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Wire the pipeline facet behind the host's `PipelineRouter` contract, then exercise it through the host's request pipeline:
|
||||
|
||||
```ts
|
||||
import { createInMemoryRouter } from "@suaveplan/api-router-in-memory/adapter";
|
||||
|
||||
const router = createInMemoryRouter();
|
||||
router.registerPipelineRoute({
|
||||
plugin: "billing",
|
||||
method: "POST",
|
||||
path: "/invoices",
|
||||
handler: async (ctx) => ({ ok: true }),
|
||||
options: { auth: "required", runtimeValidation: true },
|
||||
statusMap: { "BILLING.INSUFFICIENT_FUNDS": 402 },
|
||||
});
|
||||
|
||||
// The host's request pipeline consumes this as a PipelineRouter:
|
||||
const route = router.matchPipeline("POST", "http://h.local/invoices");
|
||||
// route.plugin === "billing"; route.statusMap["BILLING.INSUFFICIENT_FUNDS"] === 402
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class InMemoryRouter extends @suaveplan/api-router.Router`
|
||||
- `constructor(options?: { plugin?: string })`
|
||||
- Inherits `get/post/put/patch/delete/head/options/all`, `match`, `mount`, `collidesWith`, `use`, `chain`.
|
||||
- `registerPipelineRoute(reg: InMemoryPipelineRegistration): void`
|
||||
- `unregisterPipelineRoute(method: string, path: string): void`
|
||||
- `unregisterPipelinePlugin(plugin: string): void`
|
||||
- `listPipelineRoutes(): readonly InMemoryPipelineRoute[]`
|
||||
- `matchPipeline(method: string, url: string): InMemoryPipelineRoute | null`
|
||||
- `createInMemoryRouter(options?): InMemoryRouter` — factory returning the concrete type so callers can reach the pipeline-facet methods without a cast.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Verb-method registration is inherited unchanged from `@suaveplan/api-router.Router` — there is no Hono mirroring to perform, so the parent class's behaviour is the production behaviour. The pipeline facet is a separate `Map`-backed table because the host stores extra per-route metadata (`statusMap`, plugin ownership) the abstract router does not need; keeping them parallel mirrors the `HonoRouter` layering and makes the conformance suites apply identically. `createInMemoryRouter` returns the concrete `InMemoryRouter` type (not the abstract `Router`) so the pipeline-facet methods are reachable directly — this is the small, deliberate divergence from `createHonoRouter`, which hides the concrete class behind the abstract interface.
|
||||
@@ -0,0 +1,64 @@
|
||||
# `blue-green-slot` — hot-reload primitive for the in-memory router
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`InMemoryBlueGreenSlot` is the `BlueGreenSlot` implementation paired with `InMemoryRouter`. It is a thin extension of `@suaveplan/api-router`'s reference `InMemoryBlueGreenSlot`, kept locally so consumers of `@suaveplan/api-router-in-memory` import their router adapter AND their slot from a single package — symmetric with `@suaveplan/api-router-hono`'s `HonoBlueGreenSlot`.
|
||||
|
||||
The reference implementation in `api-router` already satisfies every invariant the conformance suite asserts (set/flip/drain/dispose/abort), so this class adds no behaviour; it exists solely to keep the public surface uniform.
|
||||
|
||||
## Features
|
||||
|
||||
- Inherits the reference impl's three-position state machine (`live` / `shadow` / `draining`).
|
||||
- `flip()` returns a `DrainingHandle` whose `settled` promise resolves once all in-flight requests against the previous live slot complete.
|
||||
- `dispose(slotId, { timeoutMs })` races a timer against drain settle; on timeout, aborts the slot's `AbortController` and throws `ROUTER.SLOT_DISPOSE_TIMEOUT`.
|
||||
- `acquire(slotId)` / `release(slotId)` are wired by the router so the pending counter reflects in-flight dispatch.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { InMemoryBlueGreenSlot } from "@suaveplan/api-router-in-memory/blue-green-slot";
|
||||
|
||||
const slot = new InMemoryBlueGreenSlot("billing", () => "v1");
|
||||
slot.setShadow(() => "v2");
|
||||
const draining = slot.flip();
|
||||
// slot.live now serves v2; draining points at the previous v1 slot.
|
||||
await draining.settled;
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Drive a hot-reload from a manifest-driven loader, with abort-on-timeout safety:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createInMemoryBlueGreenSlot,
|
||||
} from "@suaveplan/api-router-in-memory/blue-green-slot";
|
||||
|
||||
const slot = createInMemoryBlueGreenSlot("billing", () => "v1");
|
||||
slot.setShadow(() => "v2");
|
||||
const drain = slot.flip();
|
||||
|
||||
try {
|
||||
await slot.dispose(drain.slotId, { timeoutMs: 50 });
|
||||
} catch (err) {
|
||||
// dispose timed out — the AbortController on the previous slot has
|
||||
// already fired; in-flight handlers should observe their AbortSignal
|
||||
// and unwind. Bubble the error up to the reload driver.
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class InMemoryBlueGreenSlot extends @suaveplan/api-router.InMemoryBlueGreenSlot`
|
||||
- `constructor(plugin: string, initialHandler: SlotHandler)`
|
||||
- Inherits `live`, `shadow`, `setShadow`, `flip`, `dispose`, `acquire`, `release`.
|
||||
- `createInMemoryBlueGreenSlot(plugin, initialHandler): BlueGreenSlot` — convenience factory typed as the abstract interface.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The subclass exists to keep the package's exported surface aligned with `api-router-hono` (HonoBlueGreenSlot is its own concrete class because the live pointer is one mutable field that swaps atomically — see that package's notes). For the in-memory adapter there is no driver-specific work, so this is intentionally a no-op extension. The conformance suite in `api-router/testing` accepts either the parent class or this subclass.
|
||||
@@ -0,0 +1,66 @@
|
||||
# `error-codes` — `@suaveplan/api-router-in-memory` error registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the stable `ROUTER_IN_MEMORY.*` error-code namespace used by every production path in `@suaveplan/api-router-in-memory`. The registry is consumed by the host's error-envelope serializer, by the observability stack for span attribution, and by downstream tests asserting on stable codes rather than string-matched messages.
|
||||
|
||||
Every code listed here MUST have at least one production-path throw site. The unit test for this module enumerates the registry against the package source and refuses to compile when a code is added without a corresponding throw.
|
||||
|
||||
## Features
|
||||
|
||||
- Stable, dot-namespaced codes (`ROUTER_IN_MEMORY.ROUTE_COLLISION`, `ROUTER_IN_MEMORY.INVALID_PREFIX`).
|
||||
- `RouterInMemoryError` subclass of `@suaveplan/error`'s `SuaveplanError` carrying typed `code`, optional `cause`, and a free-form `context` record.
|
||||
- Compatible with the `SuaveplanError` wire-envelope serializer — codes survive serialization across worker boundaries and HTTP responses.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
ROUTER_IN_MEMORY_CODES,
|
||||
RouterInMemoryError,
|
||||
} from "@suaveplan/api-router-in-memory/error-codes";
|
||||
|
||||
throw new RouterInMemoryError(
|
||||
ROUTER_IN_MEMORY_CODES.ROUTE_COLLISION,
|
||||
"duplicate GET /users",
|
||||
{ context: { method: "GET", path: "/users" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Pattern-match against the typed code union in the host's error boundary:
|
||||
|
||||
```ts
|
||||
import type { RouterInMemoryCode } from "@suaveplan/api-router-in-memory/error-codes";
|
||||
import {
|
||||
ROUTER_IN_MEMORY_CODES,
|
||||
RouterInMemoryError,
|
||||
} from "@suaveplan/api-router-in-memory/error-codes";
|
||||
|
||||
function classify(err: unknown): RouterInMemoryCode | "unknown" {
|
||||
if (err instanceof RouterInMemoryError) {
|
||||
if (err.code === ROUTER_IN_MEMORY_CODES.ROUTE_COLLISION) {
|
||||
return ROUTER_IN_MEMORY_CODES.ROUTE_COLLISION;
|
||||
}
|
||||
if (err.code === ROUTER_IN_MEMORY_CODES.INVALID_PREFIX) {
|
||||
return ROUTER_IN_MEMORY_CODES.INVALID_PREFIX;
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `ROUTER_IN_MEMORY_CODES` — frozen record mapping `<DOMAIN>` to the dot-namespaced literal.
|
||||
- `RouterInMemoryCode` — union of every value in `ROUTER_IN_MEMORY_CODES`.
|
||||
- `RouterInMemoryError` — `SuaveplanError` subclass; constructor `(code, message, { cause?, context? })`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` returns a `const` record whose values are `${NAMESPACE}.${KEY}` literals — the literal type is preserved into `RouterInMemoryCode` so any switch over the union is exhaustively checked by TypeScript. The error class sets `this.name = "RouterInMemoryError"` for accurate stack traces. The set of codes is intentionally minimal — the in-memory adapter inherits most semantics from the abstract `Router`'s `RouterError`; `ROUTER_IN_MEMORY.*` covers only adapter-specific failures.
|
||||
@@ -0,0 +1,68 @@
|
||||
# `pipeline` — in-memory PipelineRouter facet
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`PipelineRouteTable` is the in-memory metadata store that backs `InMemoryRouter`'s pipeline facet. It stores per-route plugin ownership, options, status-code overrides, and handler references; it is the source of truth `InMemoryRouter.matchPipeline(method, url)` consults at request time.
|
||||
|
||||
The table is structurally compatible with `@suaveplan/api-types/pipeline-router`'s `PipelineRouter` contract — `match()` returns either `null` (the host's 404 path) or a value assignable to `PipelineRoute`. The table also supports hot-reload teardown via `unregisterPlugin(plugin)` and admin-API listing via `list()`.
|
||||
|
||||
## Features
|
||||
|
||||
- Plugin-ownership-aware collision detection: same `(method, path-shape)` registered by a different plugin throws `ROUTER_IN_MEMORY.ROUTE_COLLISION`; same plugin re-registering overwrites (the hot-reload path).
|
||||
- `:param` segment compilation with URI-decoded extraction at match time.
|
||||
- Trailing-slash normalisation: `/x/` and `/x` resolve to the same shape.
|
||||
- Case-insensitive method matching: `register({ method: "post" })` is reachable via `match("POST", url)`.
|
||||
- O(N) match against the registration set — acceptable for the in-process test target; production deployments use the host's compiled router.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { PipelineRouteTable } from "@suaveplan/api-router-in-memory/pipeline";
|
||||
|
||||
const table = new PipelineRouteTable();
|
||||
table.register({
|
||||
plugin: "billing",
|
||||
method: "GET",
|
||||
path: "/invoices/:id",
|
||||
handler: (ctx) => ({ id: (ctx as { params: { id: string } }).params.id }),
|
||||
});
|
||||
const match = table.match("GET", "http://h.local/invoices/i_42");
|
||||
// match.plugin === "billing"; match.params.id === "i_42"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Hot-reload an entire plugin's routes in one cycle:
|
||||
|
||||
```ts
|
||||
import { PipelineRouteTable } from "@suaveplan/api-router-in-memory/pipeline";
|
||||
|
||||
const table = new PipelineRouteTable();
|
||||
const v1 = { plugin: "billing", method: "GET", path: "/x", handler: () => "v1" };
|
||||
table.register(v1);
|
||||
|
||||
// Hot-reload: drop everything owned by billing, register the new version.
|
||||
table.unregisterPlugin("billing");
|
||||
table.register({ ...v1, handler: () => "v2" });
|
||||
|
||||
const snapshot = table.list();
|
||||
// snapshot[0].handler now returns "v2"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class PipelineRouteTable`
|
||||
- `register(reg: InMemoryPipelineRegistration): void` — register/overwrite a route; throws `ROUTER_IN_MEMORY.ROUTE_COLLISION` on cross-plugin collision.
|
||||
- `unregister(method: string, path: string): void` — remove one entry; no-op when absent.
|
||||
- `unregisterPlugin(plugin: string): void` — remove every entry owned by `plugin`.
|
||||
- `list(): readonly InMemoryPipelineRoute[]` — snapshot of every entry.
|
||||
- `match(method: string, url: string): InMemoryPipelineRoute | null` — resolve one URL; populates `params` from `:param` segments.
|
||||
- `size(): number` — coverage helper.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The shape key erases parameter names (`/users/:id` and `/users/:other` collide) so the host's plugin-isolation invariant is preserved. Match iterates in insertion order — every adapter is free to choose, but the in-memory adapter picks first-registered-wins to make the conformance suite deterministic. Path normalisation strips a single trailing slash; collapsing repeated slashes is out of scope (production deployments handle that at the L7 edge).
|
||||
@@ -0,0 +1,53 @@
|
||||
# `testing` — downstream-consumer test harness
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`createInMemoryRouterHarness()` is the canonical "spin up an in-memory router for a test" entry point. It bundles a fresh `InMemoryRouter` and a pinned `InMemoryBlueGreenSlot` factory so foundation + plugin tests stop hand-rolling router shims.
|
||||
|
||||
The harness exists because the testing surface has historically grown by accretion — a handful of plugin tests inline their own one-off router stub, every one slightly different. Centralising the shape here means future Router/PipelineRouter contract changes propagate through one factory rather than dozens of test files.
|
||||
|
||||
## Features
|
||||
|
||||
- One call returns a router + slot pair ready for the conformance suites or any downstream pipeline test.
|
||||
- Slot factory captures the plugin name + initial handler shape the host's reload driver expects.
|
||||
- Pure helper — no global state, no shared mutable references between calls.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createInMemoryRouterHarness } from "@suaveplan/api-router-in-memory/testing";
|
||||
|
||||
const { router } = createInMemoryRouterHarness();
|
||||
router.get("/x", () => null);
|
||||
// router.match("GET", "/x") returns the registered match.
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Drive a hot-reload through the slot the harness produces:
|
||||
|
||||
```ts
|
||||
import { createInMemoryRouterHarness } from "@suaveplan/api-router-in-memory/testing";
|
||||
|
||||
const harness = createInMemoryRouterHarness({ plugin: "billing" });
|
||||
const slot = harness.slot("billing", () => "v1");
|
||||
slot.setShadow(() => "v2");
|
||||
const draining = slot.flip();
|
||||
await slot.dispose(draining.slotId, { timeoutMs: 100 });
|
||||
// slot.live now serves v2; the previous v1 slot has drained.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface InMemoryRouterHarness`
|
||||
- `router: InMemoryRouter`
|
||||
- `slot(plugin: string, initialHandler: () => unknown): InMemoryBlueGreenSlot`
|
||||
- `createInMemoryRouterHarness(options?: { plugin?: string }): InMemoryRouterHarness`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The harness is deliberately minimal — it does not pre-register any routes, does not couple to the host's `createTestHost`, and does not expose any assertion helpers. Those concerns belong to the host's `@suaveplan/api-host/testing` createTestHost adapter table, which wires this package as the `router: "in-memory"` option. Downstream tests that need plugin-specific routes call `harness.router.get(...)` after construction.
|
||||
Reference in New Issue
Block a user