84 lines
3.7 KiB
Markdown
84 lines
3.7 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
|
|
|
# Capability Spec: api-router-in-memory
|
|
|
|
**Package**: `@suaveplan/api-router-in-memory`
|
|
**Status**: SHIPPED
|
|
|
|
## Requirements
|
|
|
|
### Requirement: Dispatch without a socket
|
|
|
|
The package SHALL implement `Router` such that match resolution runs the full request pipeline in-process and returns route data without binding any socket, port, or HTTP listener.
|
|
|
|
#### Scenario: match resolves without network I/O
|
|
|
|
- **WHEN** a route is registered AND `router.match("GET", "/x")` is called
|
|
- **THEN** the result is a route match AND no network socket is opened
|
|
|
|
**Verified by:** `src/adapter/adapter.test.ts::"dispatch sanity — no network sockets opened by match"`
|
|
|
|
---
|
|
|
|
### Requirement: Implements both Router and PipelineRouter contracts
|
|
|
|
`InMemoryRouter` SHALL satisfy both the abstract `Router` contract from `@suaveplan/api-router` (verb registration, mount, collision detection, param parsing) AND the `PipelineRouter` contract from `@suaveplan/api-host` (`matchPipeline` returning a structurally compatible `PipelineRoute`).
|
|
|
|
#### Scenario: every Router invariant passes
|
|
|
|
- **WHEN** `runRouterConformance({ label: "in-memory", router, slot })` runs
|
|
- **THEN** every invariant reports passing
|
|
|
|
#### Scenario: every PipelineRouter invariant passes
|
|
|
|
- **WHEN** `runPipelineRouterConformance({ label: "...", build })` runs against `matchPipeline`
|
|
- **THEN** every invariant reports passing
|
|
|
|
**Verified by:** `tests/conformance/api-router.test.ts` AND `tests/conformance/api-host.test.ts`
|
|
|
|
---
|
|
|
|
### Requirement: Pipeline facet supports hot-reload teardown
|
|
|
|
`InMemoryRouter` SHALL expose `registerPipelineRoute`, `unregisterPipelineRoute`, `unregisterPipelinePlugin`, `listPipelineRoutes`, and `matchPipeline`. `unregisterPipelinePlugin(plugin)` SHALL remove every route owned by `plugin` in one call. Same-plugin re-registration SHALL overwrite; cross-plugin collision SHALL throw `ROUTER_IN_MEMORY.ROUTE_COLLISION`.
|
|
|
|
#### Scenario: register/match/list/unregister/unregisterPlugin
|
|
|
|
- **WHEN** routes for plugins `a` and `b` are registered and `unregisterPipelinePlugin("a")` is called
|
|
- **THEN** plugin `b`'s entries remain matchable and plugin `a`'s entries return null
|
|
|
|
#### Scenario: cross-plugin collision throws ROUTE_COLLISION
|
|
|
|
- **WHEN** plugin `b` attempts to register a method+path-shape already owned by plugin `a`
|
|
- **THEN** `RouterInMemoryError` with code `ROUTER_IN_MEMORY.ROUTE_COLLISION` is thrown
|
|
|
|
**Verified by:** `src/adapter/adapter.test.ts::"pipeline facet — register/match/list/unregister/unregisterPlugin"` AND `src/pipeline/pipeline.test.ts::"cross-plugin collision throws ROUTE_COLLISION"`
|
|
|
|
---
|
|
|
|
### Requirement: Options validated at construction boundary
|
|
|
|
`InMemoryRouter` SHALL validate constructor options with a Zod schema. Invalid options SHALL throw `RouterInMemoryError` with code `ROUTER_IN_MEMORY.INVALID_PREFIX` carrying the offending value in `context`.
|
|
|
|
#### Scenario: invalid plugin option throws INVALID_PREFIX
|
|
|
|
- **WHEN** `new InMemoryRouter({ plugin: "bad/plugin" })` is called
|
|
- **THEN** `RouterInMemoryError(ROUTER_IN_MEMORY.INVALID_PREFIX, ...)` is thrown
|
|
|
|
**Verified by:** `src/adapter/adapter.test.ts::"invalid plugin option throws INVALID_PREFIX"`
|
|
|
|
---
|
|
|
|
### Requirement: Test harness for downstream consumers
|
|
|
|
The package SHALL expose `createInMemoryRouterHarness()` returning a fresh `InMemoryRouter` and a pinned `InMemoryBlueGreenSlot` factory, replacing the hand-rolled router shims across foundation + plugin tests.
|
|
|
|
#### Scenario: harness returns a fresh router + slot pair
|
|
|
|
- **WHEN** `createInMemoryRouterHarness()` is called twice
|
|
- **THEN** each call yields a distinct router AND distinct slot instances
|
|
|
|
**Verified by:** `src/testing/testing.test.ts::"slot factory produces fresh InMemoryBlueGreenSlot instances"`
|
|
|
|
<!-- markdownlint-enable MD024 -->
|