3.4 KiB
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 throwsROUTER_IN_MEMORY.ROUTE_COLLISION; same plugin re-registering overwrites (the hot-reload path). :paramsegment compilation with URI-decoded extraction at match time.- Trailing-slash normalisation:
/x/and/xresolve to the same shape. - Case-insensitive method matching:
register({ method: "post" })is reachable viamatch("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
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:
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 PipelineRouteTableregister(reg: InMemoryPipelineRegistration): void— register/overwrite a route; throwsROUTER_IN_MEMORY.ROUTE_COLLISIONon cross-plugin collision.unregister(method: string, path: string): void— remove one entry; no-op when absent.unregisterPlugin(plugin: string): void— remove every entry owned byplugin.list(): readonly InMemoryPipelineRoute[]— snapshot of every entry.match(method: string, url: string): InMemoryPipelineRoute | null— resolve one URL; populatesparamsfrom:paramsegments.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).