@naturalcycles/nodejs-lib
Version:
Standard library for Node.js
80 lines (79 loc) • 2.66 kB
JavaScript
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,
});
}
}