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

3.8 KiB

adapter — InMemoryRouter

(no submodules)

Purpose

InMemoryRouter is The in-memory router adapter for @suaveplan/api. It implements both the abstract Router contract from @suaveplan/api-router (verb registration, collision detection, mount/prefix rewriting, param parsing) AND the PipelineRouter contract from @suaveplan/api-router (route metadata sidecar with plugin ownership, status-map propagation, and hot-reload teardown).

Designed as a real, production-grade adapter that happens to keep its state in process memory — not a test double. The class passes both runRouterConformance and runPipelineRouterConformance end-to-end.

Features

  • Single concrete class satisfying two contracts (Router + PipelineRouter).
  • Zero network I/O — match()/matchPipeline() resolve from in-memory maps.
  • Plugin-isolation invariant: cross-plugin collisions throw ROUTER_IN_MEMORY.ROUTE_COLLISION; same-plugin re-registration overwrites (the hot-reload path).
  • unregisterPipelinePlugin(plugin) clears every entry owned by a plugin in one call — wired by the host's reload driver.
  • Compatible with @suaveplan/api-router-in-memory/blue-green-slot's InMemoryBlueGreenSlot for the same hot-reload primitive Stage-4 ships.

Usage: Basic

import { createInMemoryRouter } from "@suaveplan/api-router-in-memory/adapter";

const router = createInMemoryRouter({ plugin: "billing" });
router.get("/invoices/:id", () => null);

const match = router.match("GET", "/invoices/i_42");
// match.params.id === "i_42"

Usage: Advanced

Wire the pipeline facet behind the host's PipelineRouter contract, then exercise it through the host's request pipeline:

import { createInMemoryRouter } from "@suaveplan/api-router-in-memory/adapter";

const router = createInMemoryRouter();
router.registerPipelineRoute({
    plugin: "billing",
    method: "POST",
    path: "/invoices",
    handler: async (ctx) => ({ ok: true }),
    options: { auth: "required", runtimeValidation: true },
    statusMap: { "BILLING.INSUFFICIENT_FUNDS": 402 },
});

// The host's request pipeline consumes this as a PipelineRouter:
const route = router.matchPipeline("POST", "http://h.local/invoices");
// route.plugin === "billing"; route.statusMap["BILLING.INSUFFICIENT_FUNDS"] === 402

API Reference

  • class InMemoryRouter extends @suaveplan/api-router.Router
    • constructor(options?: { plugin?: string })
    • Inherits get/post/put/patch/delete/head/options/all, match, mount, collidesWith, use, chain.
    • registerPipelineRoute(reg: InMemoryPipelineRegistration): void
    • unregisterPipelineRoute(method: string, path: string): void
    • unregisterPipelinePlugin(plugin: string): void
    • listPipelineRoutes(): readonly InMemoryPipelineRoute[]
    • matchPipeline(method: string, url: string): InMemoryPipelineRoute | null
  • createInMemoryRouter(options?): InMemoryRouter — factory returning the concrete type so callers can reach the pipeline-facet methods without a cast.

Implementation Notes

Verb-method registration is inherited unchanged from @suaveplan/api-router.Router — there is no Hono mirroring to perform, so the parent class's behaviour is the production behaviour. The pipeline facet is a separate Map-backed table because the host stores extra per-route metadata (statusMap, plugin ownership) the abstract router does not need; keeping them parallel mirrors the HonoRouter layering and makes the conformance suites apply identically. createInMemoryRouter returns the concrete InMemoryRouter type (not the abstract Router) so the pipeline-facet methods are reachable directly — this is the small, deliberate divergence from createHonoRouter, which hides the concrete class behind the abstract interface.