UNPKG

@naturalcycles/nodejs-lib

Version:
80 lines (79 loc) 2.66 kB
import { Readable } from 'node:stream'; import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'; import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js'; import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'; /** * Allows to combine multiple Readables into 1 Readable. * As soon as any of the input Readables emit - the output Readable emits * (passes through). * Order is not preserved in any way, first come first served! * * Readable completes when all input Readables complete. * * @experimental */ export class ReadableCombined extends Readable { inputs; static create(inputs, opt = {}) { return new ReadableCombined(inputs, opt); } constructor(inputs, opt) { const { objectMode = true, highWaterMark } = opt; super({ objectMode, highWaterMark }); this.inputs = inputs; this.logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel); void this.run(); } logger; /** * If defined - we are in Paused mode * and should await the lock to be resolved before proceeding. * * If not defined - we are in Flowing mode, no limits in data flow. */ lock; countIn = 0; countOut = 0; countReads = 0; async run() { const { logger } = this; await pMap(this.inputs, async (input, i) => { for await (const item of input) { this.countIn++; this.logStats(); if (this.lock) { await this.lock; // lock is undefined at this point } const shouldContinue = this.push(item); this.countOut++; if (!shouldContinue && !this.lock) { this.lock = pDefer(); logger.log(`ReadableCombined.push #${i} returned false, pausing the flow!`); } } logger.log(`ReadableCombined: input #${i} done`); }); logger.log(`ReadableCombined: all inputs done!`); this.push(null); } _read() { this.countReads++; if (this.lock) { this.logger.log(`ReadableCombined._read: resuming the flow!`); // calling it in this order is important! // this.lock should be undefined BEFORE we call lock.resolve() const { lock } = this; this.lock = undefined; lock.resolve(); } } logStats() { const { countIn, countOut, countReads } = this; this.logger.debug({ countIn, countOut, countReads, }); } }