Initial Commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# marshal-response
|
||||
|
||||
## Purpose
|
||||
|
||||
This module turns the value returned by a sandboxed handler into a host
|
||||
`Response`. The sandbox-side handler returns a `SerializedResponse`
|
||||
(plain object with `status`, ordered header pairs, and base64-url
|
||||
`bodyBase64`); this module validates that shape with a Zod schema and
|
||||
constructs the corresponding WHATWG `Response` for the host's request
|
||||
pipeline to forward to the caller.
|
||||
|
||||
The validation step is what stops a misbehaving sandboxed handler
|
||||
(returning `null`, returning a primitive, returning a partial object
|
||||
missing `status`) from crashing the host pipeline downstream — and
|
||||
re-rehydrates the structurally pure JSON shape (`Array<[k, v]>` headers,
|
||||
`bodyBase64` string) back into the host's class-flavoured `Response`
|
||||
type.
|
||||
|
||||
## Features
|
||||
|
||||
- Zod-validated unmarshal — `SerializedResponseSchema` enforces
|
||||
`status ∈ [100,599]`, ordered `[name, value]` header pairs, and
|
||||
`bodyBase64: string` plus the optional `errorCode`/`errorMessage`
|
||||
pair the wire format uses for sandbox-tier exceptions.
|
||||
- Failure surfacing — non-conformant shapes raise
|
||||
`SAND_VM_BRIDGE.UNMARSHAL_FAILED` with the Zod issue list attached
|
||||
as `context.issues`.
|
||||
- Base64-url body decode — `Buffer.from(str, "base64url")` rehydrates
|
||||
the bytes; the result feeds straight into `Response`'s
|
||||
`BodyInit`-compatible `Uint8Array` slot.
|
||||
- Headers rehydration — `Array<[name, value]>` is appended onto a fresh
|
||||
`Headers` instance so duplicate-name semantics from the sandbox are
|
||||
preserved as the response leaves the bridge.
|
||||
- Bodyless-status enforcement — 204/304 responses drop the body even if
|
||||
the sandbox produced one, matching the WHATWG fetch rule the host
|
||||
pipeline expects.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { unmarshalResponse } from "@suaveplan/api-sandbox-vm-host-bridge/marshal/marshal-response";
|
||||
|
||||
const response = unmarshalResponse(await sandboxHandler(serialized));
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Use the exported schema to pre-validate cached or replayed responses
|
||||
before they re-enter the pipeline:
|
||||
|
||||
```ts
|
||||
import { SerializedResponseSchema } from "@suaveplan/api-sandbox-vm-host-bridge/marshal/marshal-response";
|
||||
|
||||
const cached = await responseCache.get(key);
|
||||
const result = SerializedResponseSchema.safeParse(cached);
|
||||
if (result.success) {
|
||||
return cached;
|
||||
}
|
||||
return rebuildFromSandbox();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `unmarshalResponse(value: unknown): Response` — synchronous; throws
|
||||
`SandboxVmBridgeError(UNMARSHAL_FAILED)` on shape failure.
|
||||
- `SerializedResponseSchema: z.ZodType<SerializedResponse>` — the Zod
|
||||
validator used internally; mirrors the contract published by
|
||||
`@suaveplan/api-types/sandbox-bridge`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Headers cross as a `Headers` instance built via `append()` so duplicates
|
||||
(multi-`Set-Cookie`, etc.) preserve their iteration order. The body
|
||||
decode skips the `Buffer.from` call for empty strings so the common
|
||||
"no body" path costs nothing. Status 204/304 force a `null` body in the
|
||||
constructed `Response` because WHATWG forbids a body on those codes —
|
||||
the bridge enforces this so a sandbox that mistakenly attaches one
|
||||
cannot break the host pipeline.
|
||||
Reference in New Issue
Block a user