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,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).