mahler
Version:
A automated task composer and HTN based planner for building autonomous system agents
279 lines • 8.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Observable = exports.Subject = exports.UnhandledObservableError = void 0;
exports.interval = interval;
const util_1 = require("util");
class UnhandledObservableError extends Error {
constructor(cause) {
super('An error in an observable was uncaught by observer', { cause });
}
}
exports.UnhandledObservableError = UnhandledObservableError;
/**
* A Subject is a special type of observable tha allows values to be
* multicasted to many observers.
*
* We use the name Subject as is the terminology used by rxjs
* https://rxjs.dev/guide/subject
*/
class Subject {
subscribers = [];
// Removes all subscribers
cleanup() {
while (this.subscribers.length > 0) {
this.subscribers.pop();
}
}
next(t) {
this.subscribers.forEach((s) => {
s.next(t);
});
}
error(e) {
this.subscribers.forEach((s) => {
s.error(e);
});
// We assume the observable operation terminates
// after an error
this.cleanup();
}
complete() {
this.subscribers.forEach((s) => {
s.complete();
});
// We assume the observable operation terminates
// after complete is called
this.cleanup();
}
subscribe(next) {
let s;
if (typeof next === 'function') {
s = {
next,
error: () => void 0,
complete: () => void 0,
};
}
else {
s = next;
}
this.subscribers.push(s);
const self = this;
return {
unsubscribe() {
const index = self.subscribers.indexOf(s);
if (index !== -1) {
self.subscribers.splice(index, 1);
}
},
};
}
}
exports.Subject = Subject;
function isPromiseLike(x) {
if (x instanceof Promise) {
return true;
}
return (x != null &&
(typeof x === 'function' || typeof x === 'object') &&
typeof x.then === 'function');
}
function isSubscribable(x) {
return x != null && typeof x.subscribe === 'function';
}
function isSyncIterable(x) {
return x != null && typeof x === 'object' && Symbol.iterator in x;
}
function isIterable(x) {
return (x != null &&
typeof x === 'object' &&
(Symbol.iterator in x || Symbol.asyncIterator in x));
}
async function processPromise(p, subscriber) {
const t = await p;
if (subscriber.closed) {
return;
}
subscriber.next(t);
subscriber.complete();
}
async function processIterable(input, subscriber) {
const items = input[Symbol.asyncIterator]();
let n = await items.next();
while (!n.done) {
if (subscriber.closed) {
return;
}
subscriber.next(n.value);
n = await items.next();
}
subscriber.complete();
}
/**
* Multiplexes an iterable so that multiple iterators can consume it
*
* This returns a function that can be called to create a new iterator. Each
* iterator will receive the same values in the same order.
*/
function multiplexIterable(input) {
const items = isSyncIterable(input)
? input[Symbol.iterator]()
: input[Symbol.asyncIterator]();
const consumers = [];
let running = false;
async function readFromInput() {
if (running || consumers.length === 0) {
return;
}
running = true;
try {
// Get the next result and pass it to all consumers waiting for
// it
const result = await Promise.resolve(items.next());
let c = consumers.shift();
// Pass the resulting value to all consumers waiting for it
while (c != null) {
c.resolve(result);
c = consumers.shift();
}
// Once all consumers have been served we will only get the next result
// once next() has been called in one of the output iterators
}
catch (e) {
consumers.forEach((c) => {
c.reject(e);
});
consumers.length = 0;
}
finally {
running = false;
}
}
return function () {
return {
[Symbol.asyncIterator]() {
return {
next() {
const promise = new Promise((resolve, reject) => {
consumers.push({ resolve, reject });
});
void readFromInput();
return promise;
},
};
},
};
};
}
function from(input) {
let items;
if (isIterable(input)) {
items = multiplexIterable(input);
}
const self = {
subscribe(next) {
let s;
if (typeof next === 'function') {
s = {
next,
// This will result in an unhandledRejection as the
// promise is not awaited below. While this is not ideal,
// those errors can be detected through the node `unhandledRejection`
// event https://nodejs.org/api/process.html#event-unhandledrejection
// when in doubt, users should pass an error handler
// QUESTION: should we panic here?
error: (e) => {
throw new UnhandledObservableError(e);
},
complete: () => void 0,
closed: false,
};
}
else {
s = { ...next, closed: false };
}
if (isSubscribable(input)) {
return input.subscribe(s);
}
if (isPromiseLike(input)) {
processPromise(input, s).catch(s.error);
}
else {
processIterable(items(), s).catch(s.error);
}
return {
unsubscribe: () => {
s.closed = true;
s.next = () => void 0;
},
};
},
map(f) {
return from(map(self, f));
},
filter(f) {
return from(filter(self, f));
},
};
return self;
}
function map(o, f) {
// QUESTION: should we memoize f so it's called at most once with
// each value? Currently, it will be N*M times where N is the number of subscriptors
// and M is the number of calls per subscriptor
return {
subscribe(subscriber) {
return o.subscribe({
...subscriber,
next: (t) => {
subscriber.next(f(t));
},
});
},
};
}
function filter(o, f) {
return {
subscribe(subscriber) {
return o.subscribe({
...subscriber,
next: (t) => {
if (f(t)) {
subscriber.next(t);
}
},
});
},
};
}
function of(...values) {
return from(values);
}
function is(x) {
return isSubscribable(x) && typeof x.map === 'function';
}
/**
* Utility function to return a value on an interval.
* This is a useful observable to build new observables from
*/
function interval(periodMs) {
// We use promisify instead of `timers/promises` because it works
// works for testing with sinon faketimers
const sleep = (0, util_1.promisify)(setTimeout);
return exports.Observable.from((async function* () {
let i = 0;
while (true) {
await sleep(periodMs);
yield i++;
}
})());
}
exports.Observable = {
of,
from,
is,
map,
filter,
interval,
};
//# sourceMappingURL=observable.js.map