# `bridge-marshal` ## Purpose Shared `RequestContext` → `SerializedRequest` and `SerializedResponse` → `Response` helpers used by BOTH bridge variants. Splitting them out keeps `bridge-direct.ts` and `bridge-wasi-stdio.ts` focused on the guest-call orchestration that differs between variants — the wire marshalling is identical because both variants exchange the same `SerializedRequest` / `SerializedResponse` shapes (the difference is the transport: linear-memory copy vs. WASI-stdio frame). The error-code mapping for sandbox-originated `errorCode` values is also shared: `SAND_PERM.DENIED` surfaces as `SandboxPermissionError`, everything else as `SAND_WASM_BRIDGE.CAPABILITY_DENIED` carrying the plugin's own code. ## Features - `marshalContextRequest(ctx)` — convert a structural `RequestContext` into the wire `SerializedRequest`. Body bytes are base64-url-no-pad encoded; headers are lowercased. - `buildHostResponse(wire, pluginName)` — convert the wire `SerializedResponse` into a host `Response`. When `wire.errorCode` is set, raise the matching sandbox error instead of returning a response. - `RequestContextLike` — minimal structural interface for the five fields the bridge reads. Defined here so neither variant has to import the host's generic `RequestContext`. ## Usage: Basic ```ts import { buildHostResponse, marshalContextRequest, type RequestContextLike, } from "../bridge/bridge-marshal.js"; async function handle(ctx: RequestContextLike): Promise { const wire = await marshalContextRequest(ctx); void wire; return buildHostResponse( { status: 200, headers: [], bodyBase64: "" }, "plugin", ); } ``` ## Usage: Advanced ```ts import { buildHostResponse } from "../bridge/bridge-marshal.js"; import { SAND_WASM_BRIDGE_CODES, SandboxWasmBridgeError, } from "../error-codes/error-codes.js"; try { const res = buildHostResponse( { status: 500, headers: [], bodyBase64: "", errorCode: "PLUGIN.RATE_LIMITED", errorMessage: "too many", }, "billing", ); void res; } catch (err) { if ( err instanceof SandboxWasmBridgeError && err.code === SAND_WASM_BRIDGE_CODES.CAPABILITY_DENIED ) { // The plugin's own code is on `err.context.sandboxErrorCode`. } throw err; } ``` ## API Reference ### `marshalContextRequest(ctx: RequestContextLike): Promise` Reads the request body via `arrayBuffer()` once and encodes the bytes as base64-url-no-pad. Headers are lowercased. ### `buildHostResponse(wire: SerializedResponse, pluginName: string): Response` Throws `SandboxPermissionError` when `wire.errorCode === "SAND_PERM.DENIED"`; otherwise throws `SandboxWasmBridgeError` with code `CAPABILITY_DENIED` for any other `errorCode`. Returns a `Response` when no error. ## Implementation Notes - Base64-url-no-pad matches the encoding used by `api-types/sandbox-bridge`'s documented body field; both bridges and Javy guests use the same encoding so the wire shape stays consistent. - The request's `body` is materialised eagerly via `arrayBuffer()` because the wasi-stdio guest cannot read from a `ReadableStream` — the entire payload must be in the envelope before it crosses the fd 0 boundary. - Capability denials route through `SAND_PERM.DENIED` regardless of variant; the bridge-local `CAPABILITY_DENIED` is reserved for plugin-originated codes outside the `SAND_PERM` namespace.