redioactive
Version:
Reactive streams for chaining overlapping promises.
1,373 lines (1,372 loc) • 48.2 kB
JavaScript
"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;