first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# `adapter` — `InMemoryRouter`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```ts
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:
```ts
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.