4.4 KiB
@suaveplan/api-router-worker-bridge — request/response marshalling across the worker boundary
Package: @suaveplan/api-router-worker-bridge
Status: PROPOSED
Type: new-package
Stage: 3 (Worker isolation infrastructure)
Why
Per design doc §6: when a plugin runs in isolated or sandboxed tier, the per-request pipeline executes steps 1–7 in the main process (intake, route match, auth, tenant, rate-limit, RBAC, validation, capability scoping), then marshals the result across MessagePort to the worker for step 8 (the user-code handler). Critically: the raw Request object is NEVER marshalled. Only the already-validated body / params / query cross the boundary. This is what lets worker-tier plugins gain isolation without losing any of the host-enforced security guarantees.
api-router-worker-bridge is the pair of half-bridges (main-side + worker-side) that implement that envelope. It sits between the main-process router (api-router-hono in Stage 4) and the worker-side request dispatcher (in api-worker-host).
What changes
Net-new package. Two halves:
ADDED exports:
./main-side—BridgeMain: accepts a route hit fromapi-host's pipeline; constructs anHttpDispatchEnvelope(validated body, params, query, principal, tenantId, requestId, trace context); sends across the worker port viaapi-worker-rpc; awaits the response envelope; rehydrates the response into aResponseobject for the main-side response-validation + compose steps../worker-side—BridgeWorker: receivesHttpDispatchEnvelopeon the worker port; reconstructs aRequestContext(capability proxies already wired byapi-worker-host); invokes the route handler; serialises the return value as anHttpResponseEnvelope; supports streaming viaTransferableReadableStream../envelope-schemas— Zod schemas forHttpDispatchEnvelopeandHttpResponseEnvelope. Validated on both sides../streaming-marshal— request-body streams AND response-body streams viaTransferable; back-pressure propagated../header-allowlist— only headers in the bridge's allowlist (or matching the configurable per-route allowlist) cross the boundary; the rest are dropped on the main side so worker code cannot readAuthorization/Cookieetc../error-codes—BRIDGE_*codes (BRIDGE_ENVELOPE_INVALID,BRIDGE_HANDLER_THROW,BRIDGE_HEADER_NOT_ALLOWED,BRIDGE_RESPONSE_NOT_SERIALISABLE,BRIDGE_REQUEST_BODY_STREAM_CLOSED).
Out of scope
- Marshalling the raw
Request— explicitly forbidden by §6. The bridge marshals only the validated parts. - Routing itself —
api-router/api-router-honoown URL matching. This package picks up after step 2. - Capability proxies —
api-db-worker-proxyetc. live in their own packages. This package only marshals the request envelope. - Auth / RBAC — those run in the main process before the bridge. The bridge merely receives the resolved
principalandtenantId.
Source mapping
Genesis-original. Design doc sections: §6 (per-request data flow, especially the "isolated / sandboxed" paragraph), §5.2a.
| Requirement | Design doc section |
|---|---|
Raw Request NEVER marshalled — only validated body/params/query |
§6 |
| Steps 1–7 + 9–11 always main-process | §6 |
Streams transferred via Transferable |
§6, §5.2a |
| Auth / RBAC / tenant resolution never run inside worker | §6 |
Verification approach
src/main-side/main-side.test.ts— given a fakeRouteMatch+ validated body, envelope is sent across pairedMessageChannel; response rehydrates.src/worker-side/worker-side.test.ts— given an envelope, handler is invoked with reconstructedctx.request; response envelope serialises.src/envelope-schemas/envelope-schemas.test.ts— Zod schemas reject malformed envelopes; valid envelopes round-trip.src/streaming-marshal/streaming-marshal.test.ts— large request body stream + large response body stream both transfer; back-pressure ack works.src/header-allowlist/header-allowlist.spec-conformance.test.ts—Authorization,Cookie,Set-CookieMUST NOT cross by default; configurable allowlist works.tests/no-raw-request.test.ts— pin the invariant: the wire envelope never contains aRequestobject reference; the raw header bag is filtered.
Migration impact
None — additive only. api-host will wire this in Stage 4 once api-router-hono lands.