Initial Commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Spec delta: api-host
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: `./testing` subpath ships the PipelineRouter conformance harness
|
||||
|
||||
The package SHALL declare `./testing` as a subpath export AND ship `runPipelineRouterConformance(factory)` plus the supporting `PipelineRouterConformanceFactory` and `PipelineRouteRegistration` types. The harness SHALL exercise the host's `PipelineRouter` contract against any adapter via a factory-supplied `(router, register, reload?)` triple, covering: register → match round-trip, `null` on miss, `statusMap` propagation, plugin ownership across reload, `:param` parsing, and collision detection.
|
||||
|
||||
The placeholder `STAGE_2_RESERVED` sentinel SHALL remain exported for back-compat so existing detection logic (`Object.keys(mod).length > 0` style) continues to discriminate "is the harness wired?" from "is the subpath reserved?".
|
||||
|
||||
The package's vite build SHALL emit `dist/testing/testing.js` AND `dist/testing/pipeline-router-conformance/pipeline-router-conformance.js` so the subpath actually resolves at runtime — these files were previously absent because the testing module was tree-shaken out of the single-entry build.
|
||||
|
||||
#### Scenario: harness drives a reference PipelineRouter through every required case
|
||||
|
||||
- **WHEN** a test calls `runPipelineRouterConformance({ label, build: () => ({ router, register, reload? }) })`
|
||||
- **THEN** `describe("PipelineRouter conformance — ${label}", ...)` registers six `it()` cases AND all of them pass against any adapter whose `register` populates a backing table consulted by `router.match`
|
||||
|
||||
#### Scenario: harness asserts collision detection throws
|
||||
|
||||
- **WHEN** the harness invokes `register({ plugin: "p", method: "GET", path: "/x" })` followed by `register({ plugin: "q", method: "GET", path: "/x" })`
|
||||
- **THEN** the second call SHALL throw — the harness uses `expect(...).toThrow()` without prescribing the error class so any adapter-specific typed error satisfies the contract
|
||||
|
||||
#### Scenario: harness asserts plugin ownership survives reload
|
||||
|
||||
- **WHEN** the factory exposes a `reload()` hook, the harness registers a route, calls `reload()`, then calls `router.match(...)`
|
||||
- **THEN** the matched route's `plugin` AND `params` survive the reload cycle
|
||||
|
||||
**Verified by:** `src/testing/pipeline-router-conformance/pipeline-router-conformance.test.ts` AND (downstream) `@suaveplan/api-router-hono`'s `src/pipeline/pipeline.conformance.test.ts`
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- 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.*"`
|
||||
Reference in New Issue
Block a user