Reference · Requests and pages

stream

signature
stream(fn, map | null=) -> response

What it does

A response sent as the function makes it. The function gets send; text given to send goes out as it is, any other value as a line of JSON. The client has the head at once and each piece as it is sent, so a large export, a job's progress or a model's reply need not be finished before the first byte goes. The options are type (the content-type, text/plain; charset=utf-8 unless given), status (200 unless given, 200 to 599, but not 204, 205 or 304, which have no body) and headers, taken as respond takes them. See Streaming.

Examples

in a program
route GET /orders.csv = stream((send) => {    send("id,total\n")    for o in db.query("select id, total from orders") { send(`${o.id},${o.total}\n`) }}, { type: "text/csv", headers: { "content-disposition": "attachment" } })route GET /log = stream((out) => { for line in tail_log() { out(line) } },    { type: "text/plain", headers: { "cache-control": "no-store" } })

A page reads a stream with listen (or receive, from a click). With the type application/x-ndjson, each line is parsed as JSON and handed over as it comes; a stream that ends is not opened again:

in a program
route GET /orders.ndjson = stream((send) => {    for o in db.query("select id, total from orders") { send(json.stringify(o) + "\n") }}, { type: "application/x-ndjson" })component Orders() {    state rows = []    listen("/orders.ndjson", (o) => { rows = rows + [o] })     // one call per line    <ul>{for o in rows => <li key={o.id}>#{o.id}: {o.total}</li>}</ul>}route GET / = <Orders/>

The value itself can be made anywhere; its status and headers say what the head will be, and its body is null, since the function makes the body while it is sent:

let s = stream((send) => send("hello"))print(s)print(s.status, s.headers)print(stream((send) => send("x"), { type: "application/x-ndjson", status: 201 }).headers)
output
<response 200>200 { "content-type": "text/plain; charset=utf-8", "x-accel-buffering": "no" }{ "content-type": "application/x-ndjson", "x-accel-buffering": "no" }

Edge cases

let s = stream((send) => { print("sending"); send("x") }, { headers: { "x-accel-buffering": "yes", "content-type": "text/csv" } })print(s.headers)
output
{ "x-accel-buffering": "yes", "content-type": "text/csv" }

Errors

A first argument that is not a function, options that are not a map, a type that is empty or holds a control character, a status outside 200 to 599 (or not an int) or one with no body, and headers respond would refuse are runtime errors, not error values. send exists only inside the function: it is not a name of the program's own.

this one fails
print(stream("id,total\n"))
output
line 1: `stream` takes a function that sends, as in stream((send) => send("...")), got str
this one fails
print(stream((send) => send("x"), { status: 199 }))
output
line 1: `stream`: status must be an int from 200 to 599, got 199
this one fails
print(stream((send) => send("x"), { status: 204 }))
output
line 1: `stream` cannot answer 204: that status has no body, and what it sends would be read as the next answer; give respond(204, null)

Chaining

A stream is a route's last value; what comes before it decides whether to stream at all. Inside, send fits a pipeline as the function at its end:

in a program
route GET /names.txt = stream((send) => { for u in db.query("select name from users") { u.name + "\n" | send } })route POST /ask = stream((send) => http.stream("https://api.example.com/v1/chat", send, {    body: { prompt: request.json.prompt, stream: true },    headers: { authorization: `Bearer ${env("API_KEY")}` },}))

See also

events, respond, http.stream, receive, listen

Thread
5Useful? Like it, or add to its thread below.

Thread 0 posts

On stream(): questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo