Files
2026-05-27 23:29:53 +01:00

2.6 KiB

marshal-response

(no submodules)

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

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:

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.