UNPKG

redioactive

Version:

Reactive streams for chaining overlapping promises.

1,373 lines (1,372 loc) 48.2 kB
"use strict"; /* eslint-disable @typescript-eslint/no-unused-vars */ Object.defineProperty(exports, "__esModule", { value: true }); exports.isAPromise = exports.isPipe = exports.literal = exports.isValue = exports.isAnError = exports.isNil = exports.nil = exports.isEnd = exports.end = void 0; /** * Redioactive is a reactive streams library for Node.js, designed to work * with native promises and typescript types. The motivation for its development * is for the processing of media streams at or faster than real time on non-real * time computer systems. * Designed for the _async/await with typescript generation_, this library is * inspired by Highland.js and adds support for configurable buffers at each stage. */ const util_1 = require("util"); const events_1 = require("events"); const http_source_1 = require("./http-source"); const http_target_1 = require("./http-target"); const { isPromise } = util_1.types; /** Constant value indicating the [[RedioEnd|end]] of a stream. */ exports.end = { end: true }; /** * Test that a value is the end of a stream. * @param t Value to test. * @return True is the value is the end of a stream. */ function isEnd(t) { return t === exports.end; } exports.isEnd = isEnd; /** Constant representing a [[RedioNil|nil]] value. */ exports.nil = { nil: true }; /** * Test a value to see if it is an [[RedioNil|empty value]]. * @param t Value to test. * @return True if the value is the _nil_ empty value. */ function isNil(t) { return t === exports.nil; } exports.isNil = isNil; /** * Test a value to see if it is an [[Error]]. * @param t Value to test. * @return True if the value is an error. */ function isAnError(t) { return util_1.types.isNativeError(t); } exports.isAnError = isAnError; /** * Test a value to see if it is an stream value, not `end`, `nil` or an error. * @param t Value to test * @return True is the value is stream value. */ function isValue(t) { return t !== exports.nil && t !== exports.end && !util_1.types.isNativeError(t); } exports.isValue = isValue; /** * Utility function to create literal values of stream items. * @typeparam T Type of value to create. * @param t Value describing the literal to create. * @return Literal value. */ function literal(o) { return o; } exports.literal = literal; class RedioFitting { constructor() { this.fittingId = RedioFitting.counter++; } } RedioFitting.counter = 0; function isPipe(x) { return (typeof x === 'object' && x.redioPipe && x.redioPipe === 'redioPipe'); } exports.isPipe = isPipe; class RedioProducer extends RedioFitting { constructor(options) { super(); this.redioPipe = 'redioPipe'; this._followers = []; this._pullCheck = new Set(); this._lastVal = new Map(); this._buffer = []; this._running = true; this._bufferSizeMax = 10; this._drainFactor = 0.7; this._debug = false; this._oneToMany = false; this._rejectUnhandled = true; this._processError = false; this._paused = false; // Pausing stop pushing until all followers pull if (options) { if (typeof options.bufferSizeMax === 'number' && options.bufferSizeMax > 0) { this._bufferSizeMax = options.bufferSizeMax; } if (typeof options.drainFactor === 'number' && options.drainFactor >= 0.0 && options.drainFactor <= 1.0) { this._drainFactor = options.drainFactor; } if (typeof options.debug === 'boolean') { this._debug = options.debug; } if (options && Object.prototype.hasOwnProperty.call(options, 'oneToMany')) { this._oneToMany = options.oneToMany; } if (options && Object.prototype.hasOwnProperty.call(options, 'rejectUnhandled')) { this._rejectUnhandled = options.rejectUnhandled; } if (options && Object.prototype.hasOwnProperty.call(options, 'processError')) { this._processError = options.processError; } } } push(x) { this._buffer.push(x); if (this._debug) { console.log( // eslint-disable-next-line prettier/prettier 'Push in fitting', this.fittingId, 'buffer now length =', this._buffer.length, 'value =', x); } if (this._buffer.length >= this._bufferSizeMax) this._running = false; if (!this._paused) { this._followers.forEach((follower) => follower.next()); } } pull(puller) { if (this._followers.length && !this._followers.find((f) => f.fittingId === puller.fittingId)) { if (this._debug) { // eslint-disable-next-line prettier/prettier console.log('Non-follower pull in fitting', this.fittingId, 'to destination', puller.fittingId, 'followers:', this._followers.map((f) => f.fittingId)); } process.nextTick(() => this._followers.forEach((follower) => follower.next())); return exports.nil; } const provideVal = !this._pullCheck.has(puller.fittingId); if (!provideVal) { if (this._debug) { console.log( // eslint-disable-next-line prettier/prettier 'Pausing on pull in fitting', this.fittingId, 'with', this._followers.length, '. Repeated pulls from', puller.fittingId); } this._paused = true; } this._pullCheck.add(puller.fittingId); if (this._debug) { console.log( // eslint-disable-next-line prettier/prettier 'Received pull at fitting source', this.fittingId, 'to destination', puller.fittingId, 'with count', this._pullCheck.size, '/', this._followers.length); } const last = this._lastVal.get(puller.fittingId); let val; if (this._pullCheck.size === this._followers.length && !last) { val = this._buffer.shift(); this._pullCheck.forEach((p) => { if (val && p !== puller.fittingId) { const last = this._lastVal.get(p); if (val !== last) this._lastVal.set(p, val); else this._lastVal.delete(p); } }); this._pullCheck.clear(); if (!this._running && this._buffer.length < this._drainFactor * this._bufferSizeMax) { this._running = true; process.nextTick(() => this.next()); } if (this._paused) { if (this._debug) { console.log('Resuming in pull for fitting', this.fittingId); } this._paused = false; process.nextTick(() => this._followers.forEach((follower) => follower.next())); } } else { val = provideVal ? (last ? last : this._buffer[0]) : undefined; if (val && provideVal) { if (val !== last) this._lastVal.set(puller.fittingId, val); else this._lastVal.delete(puller.fittingId); } } if (this._debug) // eslint-disable-next-line prettier/prettier console.log('Pull at fitting source', this.fittingId, 'to destination', puller.fittingId, 'returning', val !== undefined ? val : exports.nil); return val !== undefined ? val : exports.nil; } next() { return Promise.resolve(); } valve(valve, options) { if (this._followers.length > 0) { throw new Error('Cannot consume a stream that already has a consumer. Use fork or observe.'); } this._followers = [ new RedioMiddle(this, valve, options) ]; this._pullCheck.clear(); return this._followers[0]; } spout(spout, options) { if (this._followers.length > 0) { throw new Error('Cannot consume a stream that already has a consumer. Use fork or observe.'); } this._followers = [new RedioSink(this, spout, options)]; this._pullCheck.clear(); return this._followers[0]; } append(v, options) { return this.valve(async (t) => { if (this._debug) { console.log('Append at end', isEnd(t), 'value', t); } if (isEnd(t)) { return v; } else { return t; } }, options); } batch(n, options) { let batcher = []; const num = isPromise(n) ? n : Promise.resolve(n); return this.valve(async (t) => { const maxLength = await num; if (maxLength < 1) { throw new Error('Batch length must be greater than 0'); } if (isEnd(t)) { return batcher.length === 0 ? exports.end : batcher; } batcher.push(t); if (batcher.length >= maxLength) { const singleBatch = batcher; batcher = []; return singleBatch; } else { return exports.nil; } }, options); } collect(options) { const result = []; let ended = false; const genny = () => new Promise((resolve, reject) => { if (ended) resolve(exports.end); this.spout((t) => { if (isEnd(t)) { resolve(result); ended = true; } else if (isAnError(t)) { reject(t); } else { isValue(t) && result.push(t); } }); }); return redio(genny, options); } compact(options) { return this.valve((t) => { if (isEnd(t)) { return exports.end; } else if (t) { return t; } else { return exports.nil; } }, options); } consume(f, options) { let genResolve = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any let genReject = null; const pending = []; const genny = () => new Promise((resolve, reject) => { if (pending.length > 0) { const value = pending.shift(); if (isAnError(value)) { reject(value); } else { resolve(value); } return; } genResolve = (m) => { genResolve = null; genReject = null; resolve(m); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any genReject = (reason) => { genResolve = null; genReject = null; reject(reason); }; }); this.spout((t) => new Promise((resolve, _reject) => { const push = (m) => { if (isAnError(m)) { if (genReject) { genReject(m); } else { pending.push(m); } } else { if (genResolve) { genResolve(m); } else { pending.push(m); } } }; f((isAnError(t) && t) || null, ((isValue(t) || isEnd(t)) && t) || null, push, resolve); })); return redio(genny, options); } debounce(ms, options) { let timeout; let genResolve; let mostRecent = exports.nil; let ended = false; function wait(t) { return new Promise((resolve) => { timeout = setTimeout(() => { resolve(); timeout = null; }, t); }); } const num = isAPromise(ms) ? ms : Promise.resolve(ms); const genny = () => new Promise((resolve, _reject) => { if (ended) { resolve(exports.end); } else { genResolve = resolve; } }); this.spout(async (t) => { const w = await num; if (isEnd(t)) { timeout && clearTimeout(timeout); genResolve(mostRecent); ended = true; return; } if (timeout) { clearTimeout(timeout); } wait(w).then(() => genResolve(t)); mostRecent = t; }); return redio(genny, options); } doto(f, _options) { return this.valve((t) => { if (isValue(t)) { f(t); } return t; }); } drop(num, options) { if (typeof num === 'undefined') { num = 1; } let count = 0; return this.valve(async (t) => { if (!isEnd(t) && num) { return count++ >= (await num) ? t : exports.nil; } return exports.end; }, options); } errors(f, options) { if (options) { options.processError = true; } else { options = { processError: true }; } return this.valve(async (t) => { if (isAnError(t)) { const result = await f(t); if (typeof result === 'undefined' || (typeof result === 'object' && result === null)) { return exports.nil; } else { return result; } } else { return t; } }, options); } filter(filter, options) { return this.valve(async (t) => { if (isValue(t)) { return (await filter(t)) ? t : exports.nil; } return exports.end; }, options); } find(filter, options) { let ended = false; return this.valve(async (t) => { if (ended || isEnd(t)) { return exports.end; } if (isValue(t)) { if (await filter(t)) { ended = true; return t; } } return exports.nil; }, options); } findWhere(_props, _options) { throw new Error('Not implemented'); } group(_f, _options) { throw new Error('Not implemented'); } head(_options) { throw new Error('Not implemented'); } intersperse(_sep, _options) { throw new Error('Not implemented'); } invoke(_method, _args, _options) { throw new Error('Not implemented'); } last(_options) { throw new Error('Not implemented'); } latest(_options) { throw new Error('Not implemented'); } map(mapper, options) { return this.valve(async (t) => { if (isValue(t)) { return mapper(t); } return exports.end; }, options); } pause(paused, options) { const pauseOptions = Object.assign(this.options, options); let lastVal = exports.nil; let genResolve = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any let genReject = null; let spoutResolve = null; const genny = () => { return new Promise((resolve, reject) => { genResolve = (t) => { genResolve = null; genReject = null; resolve(t); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any genReject = (reason) => { genResolve = null; genReject = null; reject(reason); }; if (isValue(lastVal)) { if (paused(lastVal)) { if (genResolve) genResolve(lastVal); } else { if (spoutResolve) spoutResolve(); } } }); }; this.spout((t) => { lastVal = t; return new Promise((resolve, _reject) => { if (isAnError(t)) { if (genReject) genReject(t); } else { if (genResolve) genResolve(t); } spoutResolve = () => { spoutResolve = null; resolve(); }; }); }, pauseOptions); return redio(genny, pauseOptions); } pick(_properties, _options) { throw new Error('Not implemented'); } pickBy(_f, _options) { throw new Error('Not implemented'); } pluck(_prop, _options) { throw new Error('Not implemented'); } ratelimit(_num, _ms, _options) { throw new Error('Not implemented'); } reduce(_iterator, _init, _options) { throw new Error('Not implemented'); } reduce1(_iterator, _options) { throw new Error('Not implemented'); } reject(_filter, _options) { throw new Error('Not implemented'); } scan(_iterator, _init, _options) { throw new Error('Not implemented'); } scan1(_iterator, _options) { throw new Error('Not implemented'); } slice(_start, _end, _options) { throw new Error('Not implemented'); } sort(_options) { throw new Error('Not implemented'); } sortBy(_f, _options) { throw new Error('Not implemented'); } split(_options) { throw new Error('Not implemented'); } splitBy(_sep, _options) { throw new Error('Not implemented'); } stopOnError(_f, _options) { throw new Error('Not implemented'); } take(num, options) { let count = 0; const tnum = num ? num : 1; return this.valve(async (t) => { if (!isEnd(t)) { return count++ < (await tnum) ? t : exports.nil; } return exports.end; }, options); } tap(_f, _options) { throw new Error('Not implemented'); } throttle(_ms, _options) { throw new Error('Not implemented'); } uniq(_options) { throw new Error('Not implemented'); } uniqBy(_f, _options) { throw new Error('Not implemented'); } where(_props, _options) { // Filter on object properties throw new Error('Not implemented'); } // Higher order streams concat(_ys, _options) { throw new Error('Not implemented'); } flatFilter(_f, _options) { throw new Error('Not implemented'); } flatMap(mapper, options) { const localOptions = Object.assign(options || {}, { oneToMany: true }); return this.valve(async (t) => { if (!isEnd(t)) { const values = await mapper(t).toArray(); if (Array.length === 0) return exports.nil; return values; } return exports.end; }, localOptions); } flatten(_options) { // where T === Liquid<F> throw new Error('Not implemented'); } fork(options) { const identity = new RedioMiddle(this, (i) => i, options); this._followers.push(identity); return identity; } unfork(ys) { let found = false; this._followers = this._followers.filter((f) => { if (!found) found = f.fittingId === ys.fittingId; return f.fittingId !== ys.fittingId; }); if (found) { this._lastVal.delete(ys.fittingId); this._pullCheck.delete(ys.fittingId); const yp = ys; yp.push(exports.end); } else { throw new Error('Failed to find requested follower to unfork'); } } merge(_options) { throw new Error('Not implemented'); } observe(_options) { throw new Error('Not implemented'); } otherwise(_ys, _options) { throw new Error('Not implemented'); } parallel(_n, _options) { throw new Error('Not implemented'); } // TODO error handling sequence(options) { let inEnded = false; // The stream of streams has ended let outEnded = false; // The most recent stream has ended let pending = exports.nil; let inResolver = null; let outResolver = null; let streamResolver = null; this.spout((t) => new Promise((resolve, _reject) => { if (isEnd(t)) { outResolver && outResolver(exports.end); inEnded = true; resolve(); } else { outEnded = false; nextSpout(t); streamResolver = resolve; } })); function nextSpout(pipe) { return pipe.spout((s) => new Promise((resolve, _reject) => { if (isEnd(s)) { streamResolver && streamResolver(); streamResolver = null; outEnded = true; return; } outEnded = false; if (outResolver) { outResolver(s); outResolver = null; resolve(); } else { if (isNil(pending)) { pending = s; inResolver = null; } else { inResolver = () => { pending = s; resolve(); }; } } })); } const out = () => new Promise((resolve, _reject) => { if (inEnded && outEnded) { resolve(exports.end); return; } if (!isNil(pending)) { resolve(pending); pending = exports.nil; inResolver && inResolver(); inResolver = null; } else { outResolver = resolve; } }); return redio(out, options); } series(_options) { throw new Error('Not implemented'); } zip(zs, options) { let pendingT = null; let pendingZ = null; let tResolver; let zResolver; zs.spout((z) => { if (pendingZ === null) { pendingZ = z; } else { if (isEnd(z) && isEnd(pendingZ)) { zResolver(); } else { tResolver([undefined, pendingZ]); pendingZ = z; zResolver(); } } return new Promise((resolve) => { zResolver = resolve; if (pendingT) { if (isEnd(pendingT) || isEnd(pendingZ)) { tResolver(exports.end); } else { tResolver([pendingT, pendingZ]); } pendingT = null; pendingZ = null; zResolver(); } }); }); return this.valve((t) => { return new Promise((resolve) => { tResolver = resolve; if (pendingT === null) { pendingT = t; } else { console.log('Here I am!'); if (isEnd(t) && isEnd(pendingT)) { tResolver(exports.nil); } else { tResolver([pendingT, undefined]); pendingT = t; } } if (pendingZ) { if (isEnd(pendingT) || isEnd(pendingZ)) { tResolver(exports.end); } else { tResolver([pendingT, pendingZ]); } pendingT = null; pendingZ = null; zResolver(); } }); }, options); } zipEach(ys, options) { let pipeIds = []; let pendingT = null; let pendingZs = []; let tResolver; let zResolvers = []; function reset() { pendingT = null; pendingZs.forEach((pz, i) => (pendingZs[i] = isEnd(pz) ? pz : null)); zResolvers.forEach((zr) => zr()); // check for any changes in the input pipes const newIds = ys.map((y) => y.fittingId); const newPendingZs = []; const newZResolvers = []; // eslint-disable-next-line prettier/prettier if (!(newIds.length === pipeIds.length && newIds.reduce((acc, nid, i) => acc && nid === pipeIds[i], true))) { // if a pipe has appeared, moved or gone away, update the pending array and the zResolver array newIds.forEach((nid, i) => { const fid = pipeIds.findIndex((pid) => pid === nid); if (fid >= 0) { newPendingZs.push(pendingZs[fid]); newZResolvers.push(zResolvers[fid]); } else { newPendingZs.push(null); // eslint-disable-next-line @typescript-eslint/no-empty-function newZResolvers.push(() => { }); makeSpout(ys[i]); } }); pipeIds = newIds; pendingZs = newPendingZs; zResolvers = newZResolvers; } } const makeSpout = (pipe) => { pipe.spout((z) => { const zIndex = pipeIds.findIndex((id) => pipe.fittingId === id); if (zIndex < 0) return; else if (pendingZs[zIndex] === null) { pendingZs[zIndex] = z; } else { if (isEnd(z) && isEnd(pendingZs[zIndex])) { zResolvers[zIndex](); } else { tResolver([pendingT, ...pendingZs]); reset(); pendingZs[zIndex] = z; } } return new Promise((resolve) => { zResolvers[zIndex] = resolve; if (pendingT) { if (isEnd(pendingT)) { tResolver(exports.end); reset(); } else { if (pendingZs.reduce((acc, pz) => acc && pz !== null, true)) { tResolver([pendingT, ...pendingZs]); reset(); } } } }); }); }; reset(); return this.valve((t) => { return new Promise((resolve) => { tResolver = resolve; if (pendingT === null) { pendingT = t; if (pendingZs.length === 0 || isEnd(pendingT)) { if (isEnd(pendingT)) { tResolver(exports.end); pendingT = null; } else { tResolver([pendingT]); reset(); } } } else { console.log('Here I am!'); if (isEnd(t) && isEnd(pendingT)) { tResolver(exports.nil); } else { console.log('resolve with pending T'); tResolver([pendingT, ...pendingZs]); pendingT = t; } } if (pendingZs.reduce((acc, pz) => acc || pz !== null, false)) { if (isEnd(pendingT)) { tResolver(exports.end); pendingT = null; } else { if (pendingZs.reduce((acc, pz) => acc && pz !== null, true)) { tResolver([pendingT, ...pendingZs]); reset(); } } } }); }, options); } each(dotoall, options) { if (typeof dotoall === 'undefined') { dotoall = ( /*t: T*/) => { /* void */ }; } return this.spout(async (tt) => { if (isEnd(tt)) { if (options && options.debug) { console.log('Each: THE END'); } return; } if (isValue(tt) && dotoall) { await dotoall(tt); } }, options); } pipe(_stream, _options) { throw new Error('Not implemented'); } async toArray(options) { const result = []; const promisedArray = new Promise((resolve, reject) => { const sp = this.spout((tt) => { if (isEnd(tt)) { resolve(result); } else { isValue(tt) && result.push(tt); } }, options); sp.catch(reject); return sp; }); return promisedArray; } toCallback(_f) { // Just one value throw new Error('Not implemented'); } toNodeStream(_streamOptions, _options) { throw new Error('Not implemented'); } http(uri, options) { if (typeof uri !== 'string') { uri = uri.toString(); } return this.spout((0, http_source_1.httpSource)(uri, options)); } get options() { return literal({ bufferSizeMax: this._bufferSizeMax, drainFactor: this._drainFactor, debug: this._debug, rejectUnhandled: this._rejectUnhandled, processError: this._processError }); } } class RedioStart extends RedioProducer { constructor(maker, options) { super(options); this._maker = maker; process.nextTick(() => this.next()); } async next() { if (this._running) { try { const result = await this._maker(); if (Array.isArray(result)) { if (this._oneToMany) { result.forEach((x) => isValue(x) && this.push(x)); } else { // Odd situation where T is an Array<X> this.push(result); } } else if (isNil(result)) { // Don't push } else { this.push(result); } if (result !== exports.end && !(Array.isArray(result) && result.some(isEnd))) { process.nextTick(() => this.next()); } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { this.push(isAnError(err) ? err : new Error(err.toString())); process.nextTick(() => this.next()); } } } } /** * Tests of the given value is a promise, in any state. * @param o Value to test. * @typeparam T Optional type that the promise resolves to. * @return Value is a promise? */ function isAPromise(o) { return isPromise(o); } exports.isAPromise = isAPromise; class RedioMiddle extends RedioProducer { constructor(prev, middler, options) { super(Object.assign(prev.options, { processError: false }, options)); this._ready = true; this._middler = (s) => new Promise((resolve, reject) => { this._ready = false; let callIt = middler(s); if (isAnError(callIt)) { callIt = Promise.reject(callIt); } const promisy = isAPromise(callIt) ? callIt : Promise.resolve(callIt); promisy.then((t) => { this._ready = true; resolve(t); if (this._debug) { // eslint-disable-next-line prettier/prettier console.log('Fitting', this.fittingId, ': middler(', isEnd(s) ? 'THE END' : s, ') =', t); } // if (!isEnd(t)) { // this.next() // } }, (err) => { this._ready = true; reject(err); // this.next() }); }); this._prev = prev; process.nextTick(() => this.next()); } async next() { if (this._running && this._ready) { const v = this._prev.pull(this); if (this._debug) { console.log('Just called pull in valve. Fitting', this.fittingId, 'value', v); } if (isAnError(v) && !this._processError) { this.push(v); process.nextTick(() => this.next()); } else if (!isNil(v)) { try { const result = await this._middler(v); if (Array.isArray(result)) { if (this._oneToMany) { result.forEach((x) => isValue(x) && this.push(x)); if (result.some(isEnd)) { this.push(exports.end); } } else { // Odd situation where T is an Array<X> this.push(result); } if (isEnd(v) && result.length > 0 && !isEnd(result[result.length - 1])) { this.push(exports.end); } // if (!isEnd(v) && result.length > 0 && isEnd(result[result.length - 1])) { // this.push(end) // } } else if (isNil(result)) { // Don't push // if (isEnd(v)) { Note - Does not get called // this.push(end) // } } else { this.push(result); if (isEnd(v) && !isEnd(result)) { this.push(exports.end); } } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { this.push(isAnError(err) ? err : new Error(err.toString())); } finally { if (this._debug) { console.log('About to call next in', this.fittingId); } this.next(); // process.nextTick(() => this.next()) } } } } } class RedioSink extends RedioFitting { constructor(prev, sinker, options) { super(); this._ready = true; this._rejectUnhandled = true; this._thatsAllFolks = null; this._errorFn = null; this._resolve = null; this._reject = null; this._last = exports.nil; this._compoundError = []; this._debug = options && Object.prototype.hasOwnProperty.call(options, 'debug') ? options.debug : prev.options.debug; this._rejectUnhandled = options && Object.prototype.hasOwnProperty.call(options, 'rejectUnhandled') ? options.rejectUnhandled : prev.options.rejectUnhandled; this._sinker = (t) => new Promise((resolve, reject) => { this._ready = false; let callIt; if (isAnError(t)) { callIt = Promise.reject(t); } else { callIt = sinker(t); } const promisy = isAPromise(callIt) ? callIt : Promise.resolve(); promisy.then(async (_value) => { this._ready = true; resolve(); if (!isEnd(t)) { process.nextTick(() => this.next()); } else { if (this._thatsAllFolks) { this._thatsAllFolks(); } if (this._resolve && this._reject) { if (this._compoundError.length > 0) { this._reject(new Error(`Errors occurred during stream:\n${this._compoundError .map((x) => x.message) .join('\n')}`)); } else { this._resolve(this._last); } } } this._last = t; return Promise.resolve(); }, (err) => { // this._ready = true reject(err); }); }); this._prev = prev; process.nextTick(() => this.next()); } async next() { if (this._ready) { const v = this._prev.pull(this); if (this._debug) { console.log('Just called pull in spout. Fitting', this.fittingId, 'value', v); } if (!isNil(v)) { try { await this._sinker(v); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { let handled = false; const latestError = isAnError(err) ? err : new Error(err.toString()); if (this._errorFn) { handled = true; // console.log('Calling error function', err) this._errorFn(latestError); } if (this._reject) { handled = true; // console.log('Adding to error list', err) this._compoundError.push(latestError); } if (!handled) { if (this._debug || !this._rejectUnhandled) { console.log('Error: Unhandled error at end of chain:', err.message); } // Will be unhandled - thrown into asynchronous nowhere if (this._rejectUnhandled) { console.log('Here we go!!!', this._rejectUnhandled); throw err; } } } } } } done(thatsAllFolks) { this._thatsAllFolks = thatsAllFolks; return this; } catch(errFn) { this._errorFn = errFn; return this; } toPromise() { return new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } } // eslint-disable-next-line @typescript-eslint/no-explicit-any function isReadableStream(x) { return (x !== null && typeof x === 'object' && typeof x.pipe === 'function' && x.readable !== false && typeof x._read === 'function' && typeof x._readableState === 'object'); } /** Implementation of the default stream generator function. Use an override. */ function redio(args1, args2, args3) { if (typeof args1 === 'string') { // HTTP funnel return new RedioStart((0, http_target_1.httpTarget)(args1, args2), args2); } if (typeof args1 === 'function') { if (args1.length === 0) { // Function is Funnel<T> return new RedioStart(args1, args2); } // Assume function is Generator<T> const funnelGenny = () => new Promise((resolve, reject) => { const values = []; const push = (t) => { if (Array.isArray(t)) { values.concat(t); } else { values.push(t); } }; const next = () => { if (values.indexOf(exports.end) >= 0) { resolve(exports.end); } else { resolve(values); } }; try { args1(push, next); } catch (err) { reject(err); } }); const options = args2 ? args2 : {}; options.oneToMany = true; return new RedioStart(funnelGenny, options); } if (Array.isArray(args1)) { let index = 0; const options = args2; return new RedioStart(() => { if (options && options.debug) { console.log(`Generating index=${index} value=${index < args1.length ? args1[index] : 'THE END'}`); } if (index >= args1.length) { return exports.end; } return args1[index++]; }, options); } // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof args1 === 'object' && typeof args1[Symbol.iterator] === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any const it = args1[Symbol.iterator](); const options = args2; const itGenny = () => new Promise((resolve, reject) => { try { const nextThing = it.next(); if (nextThing.done) { resolve(exports.end); } else { resolve(nextThing.value ? nextThing.value : exports.nil); } } catch (err) { reject(err); } }); return new RedioStart(itGenny, options); } if (isReadableStream(args1)) { const options = args2; // eslint-disable-next-line @typescript-eslint/no-explicit-any let rejector = (err) => { console.error('Redioactive: unexpected stream error: ', err); }; let ended = false; args1.on('error', (err) => { rejector(err); }); args1.on('end', () => { ended = true; }); args1.on('close', () => { ended = true; }); if (options && options.encoding) { args1.setEncoding(options.encoding); } const strGenny = () => { return new Promise((resolve, reject) => { rejector = (err) => { reject(err); }; const chunk = args1.read(options && options.chunkSize); if (chunk !== null) { resolve(chunk); } else { if (ended) { resolve(exports.end); return; } args1.once('readable', () => { const chunk = args1.read(options && options.chunkSize); if (chunk !== null) { resolve(chunk); } else { resolve(exports.end); } }); } }); }; return new RedioStart(strGenny, options); } if (args1 instanceof events_1.EventEmitter) { const options = args3; const eventName = args2; const endName = (options && options.endEvent) || 'end'; const errorName = (options && options.errorEvent) || 'error'; let ended = false; args1.on(endName, () => { ended = true; }); const funnel = () => new Promise((resolve, reject) => { if (ended) { resolve(exports.end); return; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const resolver = (x) => { args1.removeListener(errorName, rejector); args1.removeListener(endName, ender); resolve(x); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const rejector = (err) => { args1.removeListener(eventName, resolver); args1.removeListener(endName, ender); reject(err); }; const ender = () => { args1.removeListener(errorName, rejector); args1.removeListener(eventName, resolver); resolve(exports.end); }; args1.prependOnceListener(eventName, resolver); args1.once(errorName, rejector); args1.once(endName, ender); }); return new RedioStart(funnel, options); } return null; } exports.default = redio;