UNPKG

@trpc/server

Version:

The tRPC server library

258 lines (256 loc) • 6.45 kB
//#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 Object.defineProperty(exports, "isObservable", { enumerable: true, get: function() { return isObservable; } }); Object.defineProperty(exports, "observable", { enumerable: true, get: function() { return observable; } }); Object.defineProperty(exports, "observableToAsyncIterable", { enumerable: true, get: function() { return observableToAsyncIterable; } }); Object.defineProperty(exports, "observableToPromise", { enumerable: true, get: function() { return observableToPromise; } });