3.5 KiB
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 structuralRequestContextinto the wireSerializedRequest. Body bytes are base64-url-no-pad encoded; headers are lowercased.buildHostResponse(wire, pluginName)— convert the wireSerializedResponseinto a hostResponse. Whenwire.errorCodeis 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 genericRequestContext<T>.
Usage: Basic
import {
buildHostResponse,
marshalContextRequest,
type RequestContextLike,
} from "../bridge/bridge-marshal.js";
async function handle(ctx: RequestContextLike): Promise<Response> {
const wire = await marshalContextRequest(ctx);
void wire;
return buildHostResponse(
{ status: 200, headers: [], bodyBase64: "" },
"plugin",
);
}
Usage: Advanced
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<SerializedRequest>
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
bodyis materialised eagerly viaarrayBuffer()because the wasi-stdio guest cannot read from aReadableStream— the entire payload must be in the envelope before it crosses the fd 0 boundary. - Capability denials route through
SAND_PERM.DENIEDregardless of variant; the bridge-localCAPABILITY_DENIEDis reserved for plugin-originated codes outside theSAND_PERMnamespace.