it-pushable2
Version:
Pushable iterable
236 lines (187 loc) • 4.93 kB
text/typescript
import { FIFO, Next } from './fifo.js'
interface BasePushable<T> {
end: (err?: Error) => this
push: (value: T) => this
next: () => Promise<Next<T>>
return: () => { done: boolean }
throw: (err: Error) => { done: boolean }
/**
* This property contains the number of bytes (or objects) in the queue ready to be read
*/
readableLength: number
}
export interface Pushable<T> extends AsyncIterable<T>, BasePushable<T> {}
export interface PushableV<T> extends AsyncIterable<T[]>, BasePushable<T> {}
export interface Options {
objectMode?: boolean
onEnd?: (err?: Error) => void
}
type NextResult<T> = { done: false, value: T} | { done: true }
interface getNext<T, V = T> { (buffer: FIFO<T>): NextResult<V> }
export interface ObjectPushableOptions extends Options {
objectMode: true
}
export interface BytePushableOptions extends Options {
objectMode?: false
}
export function pushable (options: BytePushableOptions): Pushable<Uint8Array>
export function pushable<T> (options?: ObjectPushableOptions): Pushable<T>
export function pushable<T> (options: Options = {}): Pushable<T> {
const getNext = (buffer: FIFO<T>): NextResult<T> => {
const next: Next<T> | undefined = buffer.shift()
if (next == null) {
return { done: true }
}
if (next.error != null) {
throw next.error
}
return {
done: next.done === true,
// @ts-expect-error
value: next.value
}
}
return _pushable<T, T, Pushable<T>>(getNext, options)
}
export function pushableV (options?: BytePushableOptions): PushableV<Uint8Array>
export function pushableV<T> (options: ObjectPushableOptions): PushableV<T>
export function pushableV<T> (options: Options = {}): PushableV<T> {
const getNext = (buffer: FIFO<T>): NextResult<T[]> => {
let next: Next<T> | undefined
const values: T[] = []
while (!buffer.isEmpty()) {
next = buffer.shift()
if (next == null) {
break
}
if (next.error != null) {
throw next.error
}
if (next.done === false) {
// @ts-expect-error
values.push(next.value)
}
}
if (next == null) {
return { done: true }
}
return {
done: next.done === true,
value: values
}
}
return _pushable<T, T[], PushableV<T>>(getNext, options)
}
function _pushable<PushType, ValueType, ReturnType> (getNext: getNext<PushType, ValueType>, options?: Options): ReturnType {
options = options ?? {}
let onEnd = options.onEnd
let buffer = new FIFO<PushType>(options)
let pushable: any
let onNext: ((next: Next<PushType>) => ReturnType) | null
let ended: boolean
const waitNext = async (): Promise<NextResult<ValueType>> => {
if (!buffer.isEmpty()) {
return getNext(buffer)
}
if (ended) {
return { done: true }
}
return await new Promise((resolve, reject) => {
onNext = (next: Next<PushType>) => {
onNext = null
buffer.push(next)
try {
resolve(getNext(buffer))
} catch (err) {
reject(err)
}
return pushable
}
})
}
const bufferNext = (next: Next<PushType>) => {
if (onNext != null) {
return onNext(next)
}
buffer.push(next)
return pushable
}
const bufferError = (err: Error) => {
buffer = new FIFO()
if (onNext != null) {
return onNext({ error: err })
}
buffer.push({ error: err })
return pushable
}
const push = (value: PushType) => {
if (ended) {
return pushable
}
return bufferNext({ done: false, value })
}
const end = (err?: Error) => {
if (ended) return pushable
ended = true
return (err != null) ? bufferError(err) : bufferNext({ done: true })
}
const _return = () => {
buffer = new FIFO()
end()
return { done: true }
}
const _throw = (err: Error) => {
end(err)
return { done: true }
}
pushable = {
[Symbol.asyncIterator] () { return this },
next: waitNext,
return: _return,
throw: _throw,
push,
end,
get readableLength () {
return buffer.size
}
}
if (onEnd == null) {
return pushable
}
const _pushable = pushable
pushable = {
[Symbol.asyncIterator] () { return this },
next () {
return _pushable.next()
},
throw (err: Error) {
_pushable.throw(err)
if (onEnd != null) {
onEnd(err)
onEnd = undefined
}
return { done: true }
},
return () {
_pushable.return()
if (onEnd != null) {
onEnd()
onEnd = undefined
}
return { done: true }
},
push,
end (err: Error) {
_pushable.end(err)
if (onEnd != null) {
onEnd(err)
onEnd = undefined
}
return pushable
},
get readableLength () {
return _pushable.readableLength
}
}
return pushable
}