UNPKG

@naturalcycles/nodejs-lib

Version:
129 lines (128 loc) 4.66 kB
import { Transform } from 'node:stream'; import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error'; import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'; import { END, SKIP } from '@naturalcycles/js-lib/types'; import { yellow } from '../../colors/colors.js'; import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js'; /** * Sync (not async) version of transformMap. * Supposedly faster, for cases when async is not needed. */ export function transformMapSync(mapper, opt = {}) { const { predicate, // defaults to "no predicate" (pass everything) errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', objectMode = true, signal, } = opt; const started = Date.now(); let index = -1; let countOut = 0; let isSettled = false; let errors = 0; const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel); return new Transform({ objectMode, highWaterMark: 1, ...opt, transform(chunk, _, cb) { // Stop processing if isSettled if (isSettled) return cb(); const currentIndex = ++index; try { // map and pass through const v = mapper(chunk, currentIndex); if (v === END) { isSettled = true; // will be checked later logger.log(`transformMapSync END received at index ${currentIndex}`); _assert(signal, 'signal is required when using END'); signal.abort(new Error(PIPELINE_GRACEFUL_ABORT)); return cb(); } if (v === SKIP) { // do nothing, don't push return cb(); } if (!predicate || predicate(v, currentIndex)) { countOut++; this.push(v); } cb(); // done processing } catch (err) { logger.error(err); errors++; logErrorStats(); if (onError) { try { onError(_anyToError(err), chunk); } catch { } } if (errorMode === ErrorMode.THROW_IMMEDIATELY) { isSettled = true; try { onDone?.({ ok: false, collectedErrors, countErrors: errors, countIn: index + 1, countOut, started, }); } catch (err) { logger.error(err); } // Emit error immediately return cb(err); } if (errorMode === ErrorMode.THROW_AGGREGATED) { collectedErrors.push(err); } cb(); } }, final(cb) { // console.log('transformMap final') logErrorStats(true); if (collectedErrors.length) { try { onDone?.({ ok: false, collectedErrors, countErrors: errors, countIn: index + 1, countOut, started, }); } catch (err) { logger.error(err); } // emit Aggregated error cb(new AggregateError(collectedErrors, `transformMapSync resulted in ${collectedErrors.length} error(s)`)); } else { // emit no error try { onDone?.({ ok: true, collectedErrors, countErrors: errors, countIn: index + 1, countOut, started, }); } catch (err) { logger.error(err); } cb(); } }, }); function logErrorStats(final = false) { if (!errors) return; logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`); } }