import { pushable } from 'it-pushable' import merge from 'it-merge' import type { Duplex, Transform, Sink } from 'it-stream-types' interface SourceFn { (): A } type PipeSource = Iterable | AsyncIterable | SourceFn | Duplex type PipeTransform = Transform | Duplex type PipeSink = Sink | Duplex type PipeOutput = A extends Sink ? ReturnType : A extends Duplex ? ReturnType : never // single item pipe output includes pipe source types type SingleItemPipeOutput = A extends Iterable ? A : A extends AsyncIterable ? A : A extends SourceFn ? ReturnType : A extends Duplex ? A['source'] : PipeOutput type PipeFnInput = A extends Iterable ? A : A extends AsyncIterable ? A : A extends SourceFn ? ReturnType : A extends Transform ? ReturnType : A extends Duplex ? A['source'] : never // one item, just a pass-through export function pipe< A extends PipeSource > ( source: A ): SingleItemPipeOutput // two items, source to sink export function pipe< A extends PipeSource, B extends PipeSink> > ( source: A, sink: B ): PipeOutput // three items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeSink> > ( source: A, transform1: B, sink: C ): PipeOutput // many items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeSink> > ( source: A, transform1: B, transform2: C, sink: D ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, sink: E ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, sink: F ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, sink: G ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeTransform>, H extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, transform6: G, sink: H ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeTransform>, H extends PipeTransform>, I extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, transform6: G, transform7: H, sink: I ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeTransform>, H extends PipeTransform>, I extends PipeTransform>, J extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, transform6: G, transform7: H, transform8: I, sink: J ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeTransform>, H extends PipeTransform>, I extends PipeTransform>, J extends PipeTransform>, K extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, transform6: G, transform7: H, transform8: I, transform9: J, sink: K ): PipeOutput // lots of items, source to sink with transform(s) in between export function pipe< A extends PipeSource, B extends PipeTransform>, C extends PipeTransform>, D extends PipeTransform>, E extends PipeTransform>, F extends PipeTransform>, G extends PipeTransform>, H extends PipeTransform>, I extends PipeTransform>, J extends PipeTransform>, K extends PipeTransform>, L extends PipeSink> > ( source: A, transform1: B, transform2: C, transform3: D, transform4: E, transform5: F, transform6: G, transform7: H, transform8: I, transform9: J, transform10: K, sink: L ): PipeOutput export function pipe (first: any, ...rest: any[]): any { if (first == null) { throw new Error('Empty pipeline') } // Duplex at start: wrap in function and return duplex source if (isDuplex(first)) { const duplex = first first = () => duplex.source // Iterable at start: wrap in function } else if (isIterable(first) || isAsyncIterable(first)) { const source = first first = () => source } const fns = [first, ...rest] if (fns.length > 1) { // Duplex at end: use duplex sink if (isDuplex(fns[fns.length - 1])) { fns[fns.length - 1] = fns[fns.length - 1].sink } } if (fns.length > 2) { // Duplex in the middle, consume source with duplex sink and return duplex source for (let i = 1; i < fns.length - 1; i++) { if (isDuplex(fns[i])) { fns[i] = duplexPipelineFn(fns[i]) } } } return rawPipe(...fns) } export const rawPipe = (...fns: any): any => { let res while (fns.length > 0) { res = fns.shift()(res) } return res } const isAsyncIterable = (obj: any): obj is AsyncIterable => { return obj?.[Symbol.asyncIterator] != null } const isIterable = (obj: any): obj is Iterable => { return obj?.[Symbol.iterator] != null } const isDuplex = (obj: any): obj is Duplex => { if (obj == null) { return false } return obj.sink != null && obj.source != null } const duplexPipelineFn = (duplex: Duplex) => { return (source: any) => { const p = duplex.sink(source) if (p?.then != null) { const stream = pushable({ objectMode: true }) p.then(() => { stream.end() }, (err: Error) => { stream.end(err) }) let sourceWrap: () => Iterable | AsyncIterable const source = duplex.source if (isAsyncIterable(source)) { sourceWrap = async function * () { yield * source stream.end() } } else if (isIterable(source)) { sourceWrap = function * () { yield * source stream.end() } } else { throw new Error('Unknown duplex source type - must be Iterable or AsyncIterable') } return merge(stream, sourceWrap()) } return duplex.source } }