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
}
}