first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
# `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<T>`.
## Usage: Basic
```ts
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
```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<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 `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.