Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stream-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@doc-kit/core': minor
---

Generators can start on their dependency's first chunk instead of waiting for all of it: with `streamsInput: true`, a generator receives its dependency's output as an async iterable of chunks (with one thread, still whole), which `worker.stream` accepts. The cache records a streamed generator once for all the generators reading it, each from the first chunk, and lets go of a chunk once every one of them has read it.
54 changes: 53 additions & 1 deletion docs/creating-generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,61 @@ export async function* generate(input, worker) {
### Benefits of Streaming

- **Reduced memory usage** - Process data in chunks
- **Earlier downstream starts** - Next generator can begin before this one finishes
- **Earlier downstream starts** - A generator that streams its input (see below)
begins on the first chunk, before this one finishes
- **Better parallelism** - Multiple generators can work simultaneously

### Streaming Input

By default, a generator receives its dependency's output once it is complete,
collected into one array. With `streamsInput: true`, it receives it as it is
produced instead: `input` is an async iterable of the dependency's chunks, and
if the dependency streams its results too, the generator starts as soon as the
dependency does.

```mjs displayName="index.mjs"
import { generate, processChunk } from './generate.mjs';

export default {
name: 'per-chunk-generator',

description: 'Processes each chunk of metadata as it arrives',

dependsOn: '@doc-kit/core/metadata',

hasParallelProcessor: true,

streamsInput: true,

generate,
processChunk,
};
```

`worker.stream` takes the chunks as they arrive, starting each one's work while
the next ones are still being produced:

```mjs displayName="generate.mjs"
export async function* generate(input, worker) {
for await (const chunkResult of worker.stream(input, {})) {
yield chunkResult;
}
}
```

- A dependency that does not stream arrives as a single chunk, once it is
complete.
- With one thread, nothing would run alongside, so the input arrives whole, as
it does without `streamsInput`: accept both, as `worker.stream` does.
- With more than one thread, `input` can be iterated only once, and passing it
to `worker.stream` iterates it. If you need it twice, collect it into an
array first.
- The chunks are the dependency's own, in the order it yields them.
- Work that needs the whole input, such as sorting or writing one file, has to
wait for the last chunk.
- Other generators reading the same dependency are not affected: each gets
every chunk, from the first.

### Non-Streaming Generators

Some generators must collect all input before processing.
Expand Down
Loading
Loading