first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user