stream-chain
Version:
Chain functions, generators, Node streams, and Web streams into a pipeline with backpressure support.
141 lines (122 loc) • 4.36 kB
JavaScript
// @ts-self-types="./index.d.ts"
import * as defs from '../defs.js';
import gen from '../gen.js';
import fun from '../fun.js';
import dataSource from '../dataSource.js';
import asWebStream, {
isReadableWebStream,
isWritableWebStream,
isDuplexWebStream
} from '../asWebStream.js';
// Group consecutive functions into arrays (mirrors /node's groupFunctions) so the
// produceStages step can bundle each group into a single asWebStream(gen(...group))
// call — taking asWebStream's fused-executor path (exec.next) instead of one
// TransformStream per function.
const groupFunctions = (output, item, index, items) => {
if (isDuplexWebStream(item)) {
output.push(item);
return output;
}
if (!index && isReadableWebStream(item)) {
output.push({readable: item, writable: null});
return output;
}
if (index === items.length - 1 && isWritableWebStream(item)) {
output.push({readable: null, writable: item});
return output;
}
if (typeof item !== 'function') {
throw new TypeError(`Item #${index} is not a Web Streams object or function.`);
}
if (!output.length) output.push([]);
const last = output[output.length - 1];
if (Array.isArray(last)) {
last.push(item);
} else {
output.push([item]);
}
return output;
};
// Factory: returns a stage-producer bound to the chain's options. Options are
// forwarded to every newly-wrapped asWebStream stage (existing stream items
// passed in by the user keep their own settings — chain doesn't reconfigure them).
const makeProduceStages = options => item => {
if (Array.isArray(item)) {
if (!item.length) return null;
if (item.length === 1) return /** @type {any} */ (chain).asWebStream(item[0], options);
return /** @type {any} */ (chain).asWebStream(/** @type {any} */ (chain).gen(...item), options);
}
return item;
};
const chain = (fns, options) => {
if (!Array.isArray(fns) || !fns.length) {
throw new TypeError("Chain's first argument should be a non-empty array.");
}
fns = fns
.flat(Infinity)
.filter(Boolean)
.map(fn => (defs.isFunctionList(fn) ? defs.getFunctionList(fn) : fn))
.flat(Infinity);
if (!fns.length) {
throw new TypeError("Chain's first argument is empty after flattening.");
}
const stages = fns
.reduce(groupFunctions, [])
.map(makeProduceStages(options))
.filter(s => s);
// Pipe stages together. pipeTo handles backpressure + error propagation.
for (let i = 0; i < stages.length - 1; ++i) {
const from = stages[i].readable;
const to = stages[i + 1].writable;
if (!from) {
throw new TypeError(`Stage #${i} has no readable side; cannot pipe to next stage.`);
}
if (!to) {
throw new TypeError(`Stage #${i + 1} has no writable side; cannot accept input.`);
}
from.pipeTo(to).catch(() => {});
}
const c = {
readable: stages[stages.length - 1].readable,
writable: stages[0].writable,
streams: stages,
input: stages[0],
output: stages[stages.length - 1]
};
return c;
};
// Override-hook + ChainOutput parity statics (mirrors /node and /core).
chain.none = defs.none;
chain.stop = defs.stop;
chain.Stop = defs.Stop;
chain.finalSymbol = defs.finalSymbol;
chain.finalValue = defs.finalValue;
chain.final = defs.final;
chain.isFinalValue = defs.isFinalValue;
chain.getFinalValue = defs.getFinalValue;
chain.manySymbol = defs.manySymbol;
chain.many = defs.many;
chain.isMany = defs.isMany;
chain.getManyValues = defs.getManyValues;
chain.flushSymbol = defs.flushSymbol;
chain.flushable = defs.flushable;
chain.isFlushable = defs.isFlushable;
chain.fListSymbol = defs.fListSymbol;
chain.isFunctionList = defs.isFunctionList;
chain.getFunctionList = defs.getFunctionList;
chain.setFunctionList = defs.setFunctionList;
chain.clearFunctionList = defs.clearFunctionList;
chain.toMany = defs.toMany;
chain.normalizeMany = defs.normalizeMany;
chain.combineMany = defs.combineMany;
chain.combineManyMut = defs.combineManyMut;
chain.chain = chain;
chain.chainUnchecked = chain;
chain.gen = gen;
chain.fun = fun;
chain.asWebStream = asWebStream;
chain.dataSource = dataSource;
export default chain;
export {chain, chain as chainUnchecked, gen, fun, asWebStream, dataSource};
export {isReadableWebStream, isWritableWebStream, isDuplexWebStream} from '../asWebStream.js';
export * from '../defs.js';