# `pipeline` — in-memory PipelineRouter facet _(no submodules)_ ## 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).