Files
skills/docs/api-stack/api/api-router-in-memory/src/pipeline/pipeline.md
2026-05-29 08:20:56 +01:00

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 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

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