6.1 KiB
Capability Spec Delta: api-router-hono (api-router-hono)
Package: @suaveplan/api-router-hono
Status: PROPOSED
Every requirement carries a
**Verified by:**line at authoring time. (Seeopenspec/AGENTS.mdSection 13.)
ADDED Requirements
Requirement: Direct surface implements the Router contract
The package SHALL export createHonoRouter(opts) returning a Router (from @suaveplan/api-router) backed by a Hono instance. The returned router MUST pass runRouterConformance from @suaveplan/api-router/testing.
Scenario: factory returns a conformant Router
- WHEN
createHonoRouter({ basePath: "/" })is called - THEN the result satisfies the
Routertype ANDrunRouterConformance("hono-direct", () => createHonoRouter(...))reports zero failures
Scenario: route registration is order-independent for static paths
- WHEN
/a/bis registered after/a/:p - THEN a request to
/a/bmatches the static route AND a request to/a/cmatches the param route
Verified by: src/factory/create-hono-router.spec-conformance.test.ts::"hono-direct conforms to Router contract" AND src/factory/create-hono-router.test.ts::"static routes win over param routes when registered in either order"
Requirement: Worker-proxy surface implements the Router contract
The package SHALL export createHonoRouterWorkerProxy(port, opts) returning a Router whose calls are marshalled over a MessagePort via @suaveplan/api-worker-rpc. The proxy MUST pass the same runRouterConformance suite as the direct surface — no invariant may regress under marshalling.
Scenario: proxy passes every conformance invariant
- WHEN
runRouterConformance("hono-worker-proxy", () => createHonoRouterWorkerProxy(channel.port2, ...))runs against a realMessageChannelbridged to a direct router onchannel.port1 - THEN every invariant passes AND no invariant is skipped
Scenario: AbortSignal propagates across the proxy
- WHEN a route handler is invoked through the proxy AND the caller aborts via
AbortSignal - THEN the in-flight handler on the host side observes
signal.aborted === truewithin one event-loop tick
Verified by: src/proxy/create-hono-router-worker-proxy.spec-conformance.test.ts::"hono-worker-proxy conforms to Router contract" AND src/proxy/create-hono-router-worker-proxy.test.ts::"AbortSignal aborts the host-side handler"
Requirement: Dual-surface conformance gate
The adapter SHALL pass its capability's conformance suite under BOTH the direct surface AND the worker-proxy surface in the same test run. Failing either surface MUST fail the package's pre-publish gate.
Scenario: both surfaces run side by side
- WHEN the conformance suite executes
- THEN two
describeblocks emit results — one labelledhono-direct, one labelledhono-worker-proxy— AND both report zero failures
Verified by: src/factory/create-hono-router.spec-conformance.test.ts::"hono-direct conforms to Router contract" AND src/proxy/create-hono-router-worker-proxy.spec-conformance.test.ts::"hono-worker-proxy conforms to Router contract"
Requirement: Blue-green slot management
The package SHALL expose HonoBlueGreenSlot implementing the BlueGreenSlot contract from api-router. It maintains two Hono instances and an atomic active-slot pointer. New requests route to the active slot; in-flight requests on the inactive slot complete normally. A swap() call MUST be gated on a health-probe result when strategy === "swap-on-health".
Scenario: in-flight requests complete on the old slot
- WHEN a request is in-flight on the blue slot AND
swap()cuts over to green - THEN the in-flight blue request completes its response AND any new request after
swap()lands on green
Scenario: health-gated swap rolls back on failure
- WHEN the new slot's health probe throws OR returns false
- THEN the pointer remains on the old slot AND
ROUTER_HONO_BLUE_GREEN_SWAP_FAILEDis thrown to the caller
Verified by: src/blue-green/hono-blue-green-slot.test.ts::"in-flight requests complete on the old slot after swap" AND src/blue-green/hono-blue-green-slot.test.ts::"swap rolls back when health probe fails"
Requirement: Streaming routes bypass buffering
Routes registered with streaming: true SHALL receive the raw ReadableStream body and SHALL write responses via a streaming bridge that emits chunks to the underlying socket without intermediate buffering.
Scenario: streaming handler writes chunks incrementally
- WHEN a streaming handler emits 3 chunks separated by event-loop ticks
- THEN the client observes 3 distinct read events AND no buffering across the whole response
Verified by: src/streaming/streaming-bridge.test.ts::"streaming handler emits chunks incrementally without buffering"
Requirement: Options are Zod-validated
HonoRouterOptions SHALL be parsed through a Zod schema. Invalid options (negative maxBodyBytes, non-positive streamingTimeoutMs, unknown strategy) MUST throw at factory construction.
Scenario: invalid options throw at construction
- WHEN
createHonoRouter({ maxBodyBytes: -1 } as never)is called - THEN a
ZodError-wrappedSuaveplanErrorwith codeROUTER_HONO_INVALID_OPTIONSis thrown synchronously
Verified by: src/options/hono-router-options.test.ts::"invalid options throw at factory construction"
Requirement: Telemetry instruments emitted
The adapter SHALL emit api.router.match_duration_ms (histogram, tagged with route, method, outcome) and api.router.blue_green_swaps_total (counter) on every match and every successful swap respectively.
Scenario: histogram records on every route match
- WHEN 5 requests are dispatched through
createHonoRouter - THEN the in-memory
MeterProviderrecords exactly 5 observations onapi.router.match_duration_ms
Verified by: src/factory/create-hono-router.spec-conformance.test.ts::"emits api.router.match_duration_ms on every match"