Liking cljdoc? Tell your friends :D

koine.stream

Streaming HTTP responses — Server-Sent Events, portable.

koine.http/request buffers: it hands you the whole body once the server is done. That is wrong for an LLM, where the point of stream mode is that tokens surface as they are produced. This namespace is the incremental counterpart, kept in its own file because the host seams are completely different from the buffered ones (a buffered client and a streaming client share a URL and nothing else).

The contract is INCREMENTAL DELIVERY, not "returns the same lines". A host that can only buffer does not get a slow-but-correct fallback here — it throws (rule 2). A fake stream passes every test and then fails in front of a user, which is strictly worse than an honest error.

Measured on 2026-07-27 (150 ms between server events, arrival times relative to the first byte):

hoststreams?route
JVMyesHttpResponse$BodyHandlers/ofInputStream
let-goyes(http/request {… :as :stream}) + io/line-seq
Glojureyesnet/http + chunked Body.Read into bytes.Buffer
cljgoyes(cljg.net.http/request {… :as :stream}) + cljg.stream/read-line

The cljgo row was no until 2026-07-30 — cljg.net.http buffered through io.ReadAll and exposed no reader. The :as :stream shim closed it.

Do NOT reach for BodyHandlers/ofLines on the JVM. It looks like the clean route and it is not — see the comment on the :clj branch.

Streaming HTTP responses — Server-Sent Events, portable.

`koine.http/request` buffers: it hands you the whole body once the server is
done. That is wrong for an LLM, where the point of `stream` mode is that
tokens surface *as they are produced*. This namespace is the incremental
counterpart, kept in its own file because the host seams are completely
different from the buffered ones (a buffered client and a streaming client
share a URL and nothing else).

The contract is INCREMENTAL DELIVERY, not "returns the same lines". A host
that can only buffer does **not** get a slow-but-correct fallback here — it
throws (rule 2). A fake stream passes every test and then fails in front of a
user, which is strictly worse than an honest error.

Measured on 2026-07-27 (150 ms between server events, arrival times relative
to the first byte):

| host    | streams? | route                                             |
|---------|----------|---------------------------------------------------|
| JVM     | yes      | `HttpResponse$BodyHandlers/ofInputStream`          |
| let-go  | yes      | `(http/request {… :as :stream})` + `io/line-seq`   |
| Glojure | yes      | `net/http` + chunked `Body.Read` into `bytes.Buffer` |
| cljgo   | yes      | `(cljg.net.http/request {… :as :stream})` + `cljg.stream/read-line` |

The cljgo row was `no` until 2026-07-30 — `cljg.net.http` buffered through
`io.ReadAll` and exposed no reader. The `:as :stream` shim closed it.

Do NOT reach for `BodyHandlers/ofLines` on the JVM. It looks like the clean
route and it is not — see the comment on the `:clj` branch.
raw docstring

parse-sse-lineclj/s

(parse-sse-line line)

Parse one raw SSE line.

-> {:event "delta" :data nil} for event: delta -> {:event nil :data "{…}"} for data: {…} -> nil for blank lines, comments (: ping), and fields this shape cannot carry (id, retry, unknown).

A trailing CR and/or LF is stripped, so it accepts a line either with or without its terminator. Pure; identical on every host.

Parse one raw SSE line.

-> {:event "delta" :data nil}   for `event: delta`
-> {:event nil :data "{…}"}     for `data: {…}`
-> nil                            for blank lines, comments (`: ping`), and
                                  fields this shape cannot carry (`id`,
                                  `retry`, unknown).

A trailing CR and/or LF is stripped, so it accepts a line either with or
without its terminator. Pure; identical on every host.
raw docstring

sse-postclj/s

(sse-post url headers body on-event)

POST body to url and invoke (on-event data-string) once per SSE data: line, AS IT ARRIVES. Returns {:status n} when the stream ends.

[DONE] is delivered like any other datum — the SSE framing has no idea it is a sentinel, and deciding that is the caller's job, not the transport's.

Blocks until the server closes the stream. Header values are passed through verbatim and never logged; they routinely carry credentials.

Throws on a host with no incremental route. It does not quietly buffer.

POST `body` to `url` and invoke `(on-event data-string)` once per SSE
`data:` line, AS IT ARRIVES. Returns `{:status n}` when the stream ends.

`[DONE]` is delivered like any other datum — the SSE framing has no idea it
is a sentinel, and deciding that is the caller's job, not the transport's.

Blocks until the server closes the stream. Header values are passed through
verbatim and never logged; they routinely carry credentials.

Throws on a host with no incremental route. It does not quietly buffer.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close