@trpc/server
Version:
236 lines (234 loc) • 6.1 kB
JavaScript
//#region src/observable/observable.ts
/**
* Whether a value is an observable: an object with `subscribe`.
* @public
*/
function isObservable(x) {
return typeof x === "object" && x !== null && "subscribe" in x;
}
/**
* Creates an observable. `subscribe` runs for each subscriber, and can
* return a function that cleans up when it unsubscribes.
* @example
* ```ts
* const ticks = observable<number>((observer) => {
* const timer = setInterval(() => observer.next(Date.now()), 1000);
* return () => clearInterval(timer);
* });
* ```
* @see https://trpc.io/docs/client/links#creating-a-custom-link
* @public
*/
function observable(subscribe) {
const self = {
subscribe(observer) {
let teardownRef = null;
let isDone = false;
let unsubscribed = false;
let teardownImmediately = false;
let subscribing = true;
let thrownWhileSubscribing = null;
function end(notify) {
try {
notify();
} catch (cause) {
var _thrownWhileSubscribi;
if (!subscribing) throw cause;
(_thrownWhileSubscribi = thrownWhileSubscribing) !== null && _thrownWhileSubscribi !== void 0 || (thrownWhileSubscribing = { cause });
} finally {
unsubscribe();
}
}
function unsubscribe() {
if (teardownRef === null) {
teardownImmediately = true;
return;
}
if (unsubscribed) return;
unsubscribed = true;
if (typeof teardownRef === "function") teardownRef();
else if (teardownRef) teardownRef.unsubscribe();
}
teardownRef = subscribe({
next(value) {
var _observer$next;
if (isDone) return;
(_observer$next = observer.next) === null || _observer$next === void 0 || _observer$next.call(observer, value);
},
error(err) {
if (isDone) return;
isDone = true;
end(() => {
var _observer$error;
return (_observer$error = observer.error) === null || _observer$error === void 0 ? void 0 : _observer$error.call(observer, err);
});
},
complete() {
if (isDone) return;
isDone = true;
end(() => {
var _observer$complete;
return (_observer$complete = observer.complete) === null || _observer$complete === void 0 ? void 0 : _observer$complete.call(observer);
});
}
});
subscribing = false;
if (teardownImmediately) unsubscribe();
if (thrownWhileSubscribing) throw thrownWhileSubscribing.cause;
return { unsubscribe };
},
pipe(...operations) {
return operations.reduce(pipeReducer, self);
}
};
return self;
}
function pipeReducer(prev, fn) {
return fn(prev);
}
/**
* Resolves with an observable's first value. Rejects if it errors or
* completes first. It doesn't unsubscribe after the first value.
* @internal
*/
function observableToPromise(observable) {
const ac = new AbortController();
return new Promise((resolve, reject) => {
let isDone = false;
function onDone() {
if (isDone) return;
isDone = true;
obs$.unsubscribe();
}
ac.signal.addEventListener("abort", () => {
reject(ac.signal.reason);
});
const obs$ = observable.subscribe({
next(data) {
isDone = true;
resolve(data);
onDone();
},
error(data) {
reject(data);
},
complete() {
ac.abort();
onDone();
}
});
});
}
/**
* Turns an observable into an async iterable, like Node's `events.on()`.
* Values wait in a queue until `next()` reads them. An error rejects the
* `next()` after the queued values, once. Completion, `return()` and aborting
* `signal` unsubscribe and end every pending `next()` with `{ done: true }`;
* anything the observable sends after that is ignored.
*
* If unsubscribing throws, `return()` rejects with the error, and completion
* or an error from the observable rethrows it. On abort, it rejects the
* `next()` after the queued values instead, as an error would.
* @internal
*/
function observableToAsyncIterable(observable, signal) {
const values = [];
const pending = [];
let error = null;
let finished = false;
let subscription = null;
/** Unsubscribes once, and returns what the teardown threw. */
function unsubscribe() {
if (finished) return null;
finished = true;
signal.removeEventListener("abort", abort);
try {
subscription === null || subscription === void 0 || subscription.unsubscribe();
return null;
} catch (cause) {
return { cause };
}
}
/** Settles every pending `next()`: the first with `error` if there is one. */
function settle() {
const first = error ? pending.shift() : void 0;
if (error && first) {
const { cause } = error;
error = null;
first.reject(cause);
}
for (const promise of pending.splice(0)) promise.resolve({
value: void 0,
done: true
});
}
function finish() {
const failure = unsubscribe();
settle();
if (failure) throw failure.cause;
}
function abort() {
const failure = unsubscribe();
if (failure) error = failure;
settle();
}
subscription = observable.subscribe({
next(value) {
if (finished) return;
const promise = pending.shift();
if (promise) promise.resolve({
value,
done: false
});
else values.push(value);
},
error(cause) {
if (finished) return;
error = { cause };
finish();
},
complete: finish
});
if (finished) subscription.unsubscribe();
else if (signal.aborted) abort();
else signal.addEventListener("abort", abort, { once: true });
const iterator = {
[Symbol.asyncIterator]() {
return iterator;
},
async next() {
if (values.length > 0) return {
value: values.shift(),
done: false
};
if (error) {
const { cause } = error;
error = null;
throw cause;
}
if (finished) return {
value: void 0,
done: true
};
return new Promise((resolve, reject) => {
pending.push({
resolve,
reject
});
});
},
async return() {
values.length = 0;
error = null;
finish();
return {
value: void 0,
done: true
};
}
};
return iterator;
}
//#endregion
export { observableToPromise as i, observable as n, observableToAsyncIterable as r, isObservable as t };
//# sourceMappingURL=observable-kpFFzvou.mjs.map