first commit
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user