UNPKG

@naturalcycles/nodejs-lib

Version:
63 lines (62 loc) 2.35 kB
import { Transform } from 'node:stream'; import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'; import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js'; // oxlint-disable-next-line import/no-cycle -- intentional cycle import { Pipeline } from '../pipeline.js'; import { createReadable } from '../readable/createReadable.js'; /** * Allows to "fork" away from the "main pipeline" into the "forked pipeline". * * Correctly keeps backpressure from both "downstreams" (main and forked). * * @experimental */ export function transformFork(fn, opt = {}) { const { objectMode = true, highWaterMark = 1 } = opt; const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel); let lock; const fork = createReadable([], {}, () => { // `_read` is called if (!lock) return; // We had a lock - let's Resume logger.debug(`TransformFork: resume`); const lockCopy = lock; lock = undefined; lockCopy.resolve(); }); void fn(Pipeline.from(fork)).then(() => { logger.log('TransformFork: done'); }); return new Transform({ objectMode, highWaterMark, async transform(chunk, _, cb) { // pass through to the "main" pipeline // Main pipeline should handle backpressure "automatically", // so, we're not maintaining a Lock for it this.push(chunk); if (lock) { // Forked pipeline is locked - let's wait for it to call _read await lock; // lock is undefined at this point } // pass to the "forked" pipeline const shouldContinue = fork.push(chunk); if (!shouldContinue && !lock) { // Forked pipeline indicates that we should Pause lock = pDefer(); logger.debug(`TransformFork: pause`); } // acknowledge that we've finished processing the input chunk cb(); }, async final(cb) { logger.log('TransformFork: final'); // Pushing null "closes"/ends the secondary pipeline correctly fork.push(null); // Acknowledge that we've received `null` and passed it through to the fork cb(); }, }); }