STREAM_READ_EXPOSED_HEADERS
constSTREAM_READ_EXPOSED_HEADERS: readonlystring[]
Defined in: packages/server/src/circuits/edge.ts:214
The response headers a browser must be allowed to READ off a stream-edge response.
CORS lets script see only a short safelist of response headers (cache-control, content-language,
content-length, content-type, expires, last-modified, pragma). Every header the
durable-streams protocol answers with is outside it, so a CROSS-ORIGIN reader sees NONE of them
unless the mount names them on Access-Control-Expose-Headers.
Two generations of client read these. @pgxsinkit/client’s own reader (READ_RESPONSE_HEADERS in
its circuits/long-poll.ts, which a unit test holds to this list) steers by Stream-Next-Offset,
Stream-Up-To-Date (a presence check, which is why it is easy to miss), Stream-Cursor and
Stream-Closed. Stripped of them it cannot make progress and fails the read, loudly. Clients still
in the field on @durable-streams/client (0.2.x, which pgxsinkit read through until ADR-0065
decision 6) drive their whole read loop off the same four and also read stream-sse-data-encoding
(SSE payload decoding) and etag (the stream-metadata path). Stripped of them, that client never
learns an offset: it re-requests offset=-1 forever and never switches to a live long poll, which
presents as a hot loop of hundreds of requests per second per shape with no error raised anywhere.
Both floor their retry backoff on Retry-After; without it they fall back on their own schedule.
Stream-Seq, Stream-TTL and Stream-Expires-At are read by neither. They are named here
anyway because this list is a statement about the ds protocol’s response namespace rather than about
which subset one client version happens to read today — exposing a header a response never carries
is inert, while omitting one that it does carry wedges the reader. The Producer-* headers are
deliberately absent: they belong to the write path, which does not come through this gate.
EVERY mount of createStreamGate must put these on Access-Control-Expose-Headers of the ACTUAL
(non-preflight) response. The gate cannot do it itself — it forwards the upstream response’s
headers (verbatim for an ordinary shape, minus the caching ones for a rewritten one), and an
exposure list is meaningless without the Access-Control-Allow-Origin decision that only the mount
owns.