Files
2026-05-27 23:29:53 +01:00

50 lines
3.3 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Spec delta: api-router-hono
## ADDED Requirements
### Requirement: HonoRouter pipeline facet satisfies the host's PipelineRouter contract
The package SHALL extend `HonoRouter` with a second, additive facet that produces values structurally assignable to `@suaveplan/api-host`'s `PipelineRoute` interface. The facet SHALL expose:
- `registerPipelineRoute(reg: HonoPipelineRegistration): void` — stores `(plugin, method, path, handler, options?, statusMap?)`.
- `unregisterPipelineRoute(method: string, path: string): void` — no-op when absent.
- `unregisterPipelinePlugin(plugin: string): void` — removes every entry owned by `plugin` (hot-reload teardown).
- `listPipelineRoutes(): readonly HonoPipelineRoute[]` — snapshot for admin tooling.
- `matchPipeline(method: string, url: string): HonoPipelineRoute | null` — resolves `(method, url)` to a `HonoPipelineRoute` carrying `plugin`, `path`, `options`, `handler`, `params`, `statusMap`.
The facet SHALL preserve the existing abstract `Router` surface verbatim. Registering the same `(method, path-shape)` under a DIFFERENT plugin SHALL throw `ROUTER_HONO.PIPELINE_ROUTE_COLLISION`. Re-registering the same `(plugin, method, path)` SHALL overwrite. Parameter names SHALL be erased in the collision key so `:id` and `:other` clash.
#### Scenario: facet satisfies the host's PipelineRouter conformance suite
- **WHEN** `runPipelineRouterConformance` (from `@suaveplan/api-host/testing`) is invoked with a factory that returns `{ router: { match: (m, u) => r.matchPipeline(m, u) }, register: (reg) => r.registerPipelineRoute(reg) }`
- **THEN** every harness case passes (register → match round-trip, 404, statusMap propagation, plugin ownership across reload, param parsing, collision detection)
#### Scenario: collision detection throws ROUTER_HONO.PIPELINE_ROUTE_COLLISION
- **WHEN** two plugins register the same `(method, path-shape)` on one `HonoRouter`
- **THEN** the second call throws `RouterHonoError` with `code === "ROUTER_HONO.PIPELINE_ROUTE_COLLISION"`
#### Scenario: hot-reload teardown removes plugin-owned entries
- **WHEN** `unregisterPipelinePlugin("billing")` is called after `billing` registered two pipeline routes
- **THEN** subsequent `matchPipeline(...)` calls against either route return `null` AND `listPipelineRoutes()` no longer contains a `"billing"`-owned entry
**Verified by:** `src/pipeline/pipeline.test.ts` AND `src/pipeline/pipeline.conformance.test.ts` AND `src/adapter/adapter.test.ts::"pipeline facet — register/match/list/unregister/unregisterPlugin"`
---
## MODIFIED Requirements
### Requirement: ROUTER_HONO error-code registry
The package SHALL expose `ROUTER_HONO_CODES` (`defineCodes("ROUTER_HONO", [...])`) and a `RouterHonoError` extending `SuaveplanError`. The registry MUST contain `HONO_INTERNAL_ERROR`, `INVALID_HONO_PEER`, AND `PIPELINE_ROUTE_COLLISION`. Every code MUST be thrown by at least one production path in this package.
#### Scenario: registry shape
- **WHEN** `Object.keys(ROUTER_HONO_CODES)` is inspected
- **THEN** the keys are exactly `["HONO_INTERNAL_ERROR", "INVALID_HONO_PEER", "PIPELINE_ROUTE_COLLISION"]` AND each value is `ROUTER_HONO.<name>`
**Verified by:** `src/error-codes/error-codes.test.ts::"enumerates the three declared codes"` AND `src/error-codes/error-codes.test.ts::"namespaces each entry under ROUTER_HONO.*"`