The async generator pattern
In Node 20+ and modern browsers, the ReadableStream from fetch().body + TextDecoder + a manual newline-buffer is the canonical way to consume Ollama's NDJSON. Wrapping it in an async function* generator turns the stream into something you can iterate with for await.
The wrapping pays for itself because the consumer then knows nothing about HTTP or buffers. Printing to a terminal, appending into a DOM node, piping into another stream — all of them are the same for await. It's the same slot Python's iter_lines() pattern occupies, which is why the two clients end up shaped alike.
The buffer pattern (don't skip this)
Network chunks don't align with JSON lines. A single read() might return:
'{"a":1,"b":2}\n{"c":3,'— last line is incomplete'4}\n{"e":5}\n'— first part finishes the previous line
So the order is fixed: accumulate into a buffer, split on \n, parse every complete line, and keep the partial tail in the buffer. Dropping that last step is the common mistake, and it hides well — a short answer arrives in a single read, so no tail is ever left over and the bug sails through your test. Then answers get longer and tokens start disappearing silently. This is the single most-bug-prone piece of TypeScript Ollama clients.