Initial Commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# `marshal-response`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module is the response-side counterpart to `marshal-request`. It
|
||||
converts a Fetch `Response` produced by a worker-side handler into a
|
||||
plain `SerializedResponse` envelope safe to ship across a `MessagePort`,
|
||||
and rehydrates that envelope back into a real `Response` on the main
|
||||
process so the host pipeline can apply Steps 9-11 (response validation,
|
||||
ETag, Server-Timing aggregation).
|
||||
|
||||
## Features
|
||||
|
||||
- Validates status code is in the 100-599 range (`ROUTER_BRIDGE.RESPONSE_INVALID`).
|
||||
- Preserves status text and full header list (response headers are not on
|
||||
any deny-list — the bridge only filters request-side credentials).
|
||||
- Inlines bodies up to `DEFAULT_RESPONSE_INLINE_LIMIT` (64 KiB) and
|
||||
streams everything else through the streaming module.
|
||||
- Hard cap at `DEFAULT_RESPONSE_MAX_BYTES` (16 MiB) for inline bodies;
|
||||
oversize payloads either stream or fail with
|
||||
`ROUTER_BRIDGE.BODY_TOO_LARGE`.
|
||||
- Tagged-union body shape mirrors the request side for symmetry.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { marshalResponse, rehydrateResponse } from
|
||||
"@suaveplan/api-router-worker-bridge/marshal-response";
|
||||
|
||||
const wire = await marshalResponse(
|
||||
new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
})
|
||||
);
|
||||
const reborn = rehydrateResponse(wire);
|
||||
await reborn.json(); // { ok: true }
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Stream a large attachment response via a stream id:
|
||||
|
||||
```ts
|
||||
import {
|
||||
marshalResponse,
|
||||
rehydrateResponse,
|
||||
} from "@suaveplan/api-router-worker-bridge/marshal-response";
|
||||
|
||||
const streamId = 99;
|
||||
const wire = await marshalResponse(largeDownload, {
|
||||
streamId,
|
||||
inlineBodyLimit: 0,
|
||||
});
|
||||
// caller pipes `largeDownload.body` over the streaming channel; main side:
|
||||
const reborn = rehydrateResponse(wire, mainStreamFor(streamId));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `marshalResponse(response, options?) → Promise<SerializedResponse>`.
|
||||
- `rehydrateResponse(serialized, streamBody?) → Response`.
|
||||
- `serializedResponseSchema` — Zod validator.
|
||||
- `DEFAULT_RESPONSE_INLINE_LIMIT`, `DEFAULT_RESPONSE_MAX_BYTES`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`statusText` is preserved because some clients (and HTTP/1.1 caches)
|
||||
still depend on the reason phrase. The body tagged union is identical in
|
||||
shape to `SerializedRequestBody`, but the two types are intentionally
|
||||
distinct so future divergence (e.g. server-sent-event hints) does not
|
||||
silently propagate. Status validation runs first so a `100/600` test
|
||||
case fails cleanly even when the body would also fail.
|
||||
Reference in New Issue
Block a user