Streaming responses
DaloyJS ships first-class helpers for two streaming formats that are common in HTTP APIs: Server-Sent Events (SSE) and newline-delimited JSON (NDJSON). Both helpers wrap anAsyncIterable in a backpressure-safe ReadableStream: the underlying iterator is only advanced when the consumer pulls the next chunk, so a slow client cannot cause unbounded memory growth.
They also honor an optional AbortSignal and call iterator.return() when the client disconnects, so any caller-owned resources (DB cursors, upstream fetches, message-queue subscriptions) get released cleanly.
- 01requestClientDaloyJS helperPull the next chunkReadableStream pull(), one per consumer read
- 02requestDaloyJS helperAsync generatorAdvance the iteratoriterator.next() called exactly once per pull
- 03responseDaloyJS helperClientEncode and send one frameSSE data: ... or one NDJSON line + \n
- 04asyncDaloyJS helperClientOptional keep-alive comment while idle: keep-alive every keepAliveMs
- 05noteClientDaloyJS helperDisconnect or abortrequest.signal fires, iterator.return() runs finally
The helpers live in the main barrel and in the /streaming subpath:
Server-Sent Events (SSE)
Yield either a string (sent as data: …) or an SSEMessage object with any combination of event, id, retry, comment, and data. Multi-line strings are split into one data: line per source line, and CR/LF in event / id values are sanitized.
Use sseResponse(...) when you want a fully-formed Response with the standard SSE headers (text/event-stream, cache-control: no-cache, no-transform, connection: keep-alive, and x-accel-buffering: no) already set:
A handler may also return a raw Response directly, instead of the { status, body, headers } shape. It bypasses response-schema validation (there is no schema for an opaque stream) but is still finalized like any other response: the request id, secureHeaders(), CORS, your onSend hooks, and fingerprint stripping all still apply. This is what lets you forward a stream from a library such as the Vercel AI SDK in one line:
Keep-alive comments
Pass keepAliveMs to send a : keep-alive comment frame at a fixed interval. This prevents idle proxies from closing the connection while no events are flowing.
Newline-delimited JSON (NDJSON)
Yield any JSON-serializable value; each value is encoded with JSON.stringify and terminated with a single \n. Strings are emitted as JSON strings, and values that cannot be represented as JSON throw instead of emitting invalid NDJSON.
ndjsonResponse(...) builds the same stream with application/x-ndjson headers pre-set.
Backpressure & cancellation
Both helpers use the pull() entry point of ReadableStream: they call iterator.next() exactly once per pull. The runtime decides when to pull: a slow client on a Node socket pulls slowly, a fast Cloudflare consumer pulls quickly. You never need to write throttling code.
When the request is aborted (client disconnects, request timeout fires, explicit AbortController.abort()), the stream is closed and iterator.return() is invoked so a generator's finally block runs and any underlying cursor/socket is released.
Cross-runtime compatibility
The helpers only depend on web-standard ReadableStream and TextEncoder, so the same handler works identically on Node, Bun, Deno, Cloudflare Workers, and Vercel. The DaloyJS response serializer recognizes a ReadableStream body when you set an explicit non-JSON content-type and forwards it to the runtime without buffering.
OpenAPI
OpenAPI 3.1 has no rich schema for streamed event payloads. Document streaming routes with a free-form 200 response (just { description }) and describe the event shape in prose, or attach an example string showing one or two frames.