Initial Commit
This commit is contained in:
77
packages/foundation/flow/streams/src/pipeline/merge/merge.ts
Normal file
77
packages/foundation/flow/streams/src/pipeline/merge/merge.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Merges multiple ReadableStreams into one.
|
||||
* Chunks are emitted in arrival order (race-based). The merged stream closes
|
||||
* when all input streams have closed.
|
||||
*/
|
||||
export function merge<T>(streams: ReadableStream<T>[]): ReadableStream<T> {
|
||||
if (streams.length === 0) {
|
||||
return new ReadableStream({
|
||||
start(c) {
|
||||
c.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
if (streams.length === 1) {
|
||||
return streams[0]!;
|
||||
}
|
||||
|
||||
const readers = streams.map((s) => s.getReader());
|
||||
let closedCount = 0;
|
||||
|
||||
// Pull promises: one per reader
|
||||
const pulling = new Map<
|
||||
ReadableStreamDefaultReader<T>,
|
||||
Promise<{
|
||||
reader: ReadableStreamDefaultReader<T>;
|
||||
result: ReadableStreamReadResult<T>;
|
||||
}>
|
||||
>();
|
||||
|
||||
function pullFrom(reader: ReadableStreamDefaultReader<T>): Promise<{
|
||||
reader: ReadableStreamDefaultReader<T>;
|
||||
result: ReadableStreamReadResult<T>;
|
||||
}> {
|
||||
const p = reader.read().then((result) => ({ reader, result }));
|
||||
pulling.set(reader, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Initialise pulls
|
||||
for (const reader of readers) {
|
||||
pullFrom(reader);
|
||||
}
|
||||
|
||||
return new ReadableStream<T>({
|
||||
async pull(controller) {
|
||||
while (pulling.size > 0) {
|
||||
let reader: ReadableStreamDefaultReader<T>;
|
||||
let result: ReadableStreamReadResult<T>;
|
||||
try {
|
||||
({ reader, result } = await Promise.race([
|
||||
...pulling.values(),
|
||||
]));
|
||||
} catch (err) {
|
||||
controller.error(err);
|
||||
return;
|
||||
}
|
||||
pulling.delete(reader);
|
||||
|
||||
if (result.done) {
|
||||
closedCount++;
|
||||
if (closedCount === streams.length) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
// Continue loop to race remaining readers
|
||||
} else {
|
||||
controller.enqueue(result.value);
|
||||
pullFrom(reader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
async cancel() {
|
||||
await Promise.allSettled(readers.map((r) => r.cancel()));
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user