UNPKG

@orpc/shared

Version:

> [!WARNING] > > `@orpc/shared` is an internal dependency of oRPC packages. It does not follow semver and may change at any time without notice. > Please do not use it in your project.

455 lines (442 loc) • 12.1 kB
export { group, guard, mapEntries, mapValues, omit } from 'radash'; function resolveMaybeOptionalOptions(rest) { return rest[0] ?? {}; } function toArray(value) { return Array.isArray(value) ? value : value === void 0 || value === null ? [] : [value]; } function splitInHalf(arr) { const half = Math.ceil(arr.length / 2); return [arr.slice(0, half), arr.slice(half)]; } function once(fn) { let cached; return () => { if (cached) { return cached.result; } const result = fn(); cached = { result }; return result; }; } function sequential(fn) { let lastOperationPromise = Promise.resolve(); return (...args) => { return lastOperationPromise = lastOperationPromise.catch(() => { }).then(() => { return fn(...args); }); }; } function defer(callback) { if (typeof setTimeout === "function") { setTimeout(callback, 0); } else { Promise.resolve().then(() => Promise.resolve().then(() => Promise.resolve().then(callback))); } } class AsyncIdQueue { openIds = /* @__PURE__ */ new Set(); items = /* @__PURE__ */ new Map(); pendingPulls = /* @__PURE__ */ new Map(); get length() { return this.openIds.size; } open(id) { this.openIds.add(id); } isOpen(id) { return this.openIds.has(id); } push(id, item) { this.assertOpen(id); const pending = this.pendingPulls.get(id); if (pending?.length) { pending.shift()[0](item); if (pending.length === 0) { this.pendingPulls.delete(id); } } else { const items = this.items.get(id); if (items) { items.push(item); } else { this.items.set(id, [item]); } } } async pull(id) { this.assertOpen(id); const items = this.items.get(id); if (items?.length) { const item = items.shift(); if (items.length === 0) { this.items.delete(id); } return item; } return new Promise((resolve, reject) => { const waitingPulls = this.pendingPulls.get(id); const pending = [resolve, reject]; if (waitingPulls) { waitingPulls.push(pending); } else { this.pendingPulls.set(id, [pending]); } }); } close({ id, reason } = {}) { if (id === void 0) { this.pendingPulls.forEach((pendingPulls, id2) => { pendingPulls.forEach(([, reject]) => { reject(reason ?? new Error(`[AsyncIdQueue] Queue[${id2}] was closed or aborted while waiting for pulling.`)); }); }); this.pendingPulls.clear(); this.openIds.clear(); this.items.clear(); return; } this.pendingPulls.get(id)?.forEach(([, reject]) => { reject(reason ?? new Error(`[AsyncIdQueue] Queue[${id}] was closed or aborted while waiting for pulling.`)); }); this.pendingPulls.delete(id); this.openIds.delete(id); this.items.delete(id); } assertOpen(id) { if (!this.isOpen(id)) { throw new Error(`[AsyncIdQueue] Cannot access queue[${id}] because it is not open or aborted.`); } } } function isAsyncIteratorObject(maybe) { if (!maybe || typeof maybe !== "object") { return false; } return Symbol.asyncIterator in maybe && typeof maybe[Symbol.asyncIterator] === "function"; } const fallbackAsyncDisposeSymbol = Symbol.for("asyncDispose"); const asyncDisposeSymbol = Symbol.asyncDispose ?? fallbackAsyncDisposeSymbol; class AsyncIteratorClass { #isDone = false; #isExecuteComplete = false; #cleanup; #next; constructor(next, cleanup) { this.#cleanup = cleanup; this.#next = sequential(async () => { if (this.#isDone) { return { done: true, value: void 0 }; } try { const result = await next(); if (result.done) { this.#isDone = true; } return result; } catch (err) { this.#isDone = true; throw err; } finally { if (this.#isDone && !this.#isExecuteComplete) { this.#isExecuteComplete = true; await this.#cleanup("next"); } } }); } next() { return this.#next(); } async return(value) { this.#isDone = true; if (!this.#isExecuteComplete) { this.#isExecuteComplete = true; await this.#cleanup("return"); } return { done: true, value }; } async throw(err) { this.#isDone = true; if (!this.#isExecuteComplete) { this.#isExecuteComplete = true; await this.#cleanup("throw"); } throw err; } /** * asyncDispose symbol only available in esnext, we should fallback to Symbol.for('asyncDispose') */ async [asyncDisposeSymbol]() { this.#isDone = true; if (!this.#isExecuteComplete) { this.#isExecuteComplete = true; await this.#cleanup("dispose"); } } [Symbol.asyncIterator]() { return this; } } function replicateAsyncIterator(source, count) { const queue = new AsyncIdQueue(); const replicated = []; let error; const start = once(async () => { try { while (true) { const item = await source.next(); for (let id = 0; id < count; id++) { if (queue.isOpen(id)) { queue.push(id, item); } } if (item.done) { break; } } } catch (e) { error = { value: e }; } }); for (let id = 0; id < count; id++) { queue.open(id); replicated.push(new AsyncIteratorClass( () => { start(); return new Promise((resolve, reject) => { queue.pull(id).then(resolve).catch(reject); defer(() => { if (error) { reject(error.value); } }); }); }, async (reason) => { queue.close({ id }); if (reason !== "next") { if (replicated.every((_, id2) => !queue.isOpen(id2))) { await source?.return?.(); } } } )); } return replicated; } class EventPublisher { #listenersMap = /* @__PURE__ */ new Map(); #maxBufferedEvents; constructor(options = {}) { this.#maxBufferedEvents = options.maxBufferedEvents ?? 100; } get size() { return this.#listenersMap.size; } /** * Emits an event and delivers the payload to all subscribed listeners. */ publish(event, payload) { const listeners = this.#listenersMap.get(event); if (!listeners) { return; } for (const listener of listeners) { listener(payload); } } subscribe(event, listenerOrOptions) { if (typeof listenerOrOptions === "function") { let listeners = this.#listenersMap.get(event); if (!listeners) { this.#listenersMap.set(event, listeners = /* @__PURE__ */ new Set()); } listeners.add(listenerOrOptions); return () => { listeners.delete(listenerOrOptions); if (listeners.size === 0) { this.#listenersMap.delete(event); } }; } const signal = listenerOrOptions?.signal; const maxBufferedEvents = listenerOrOptions?.maxBufferedEvents ?? this.#maxBufferedEvents; signal?.throwIfAborted(); const bufferedEvents = []; const pullResolvers = []; const unsubscribe = this.subscribe(event, (payload) => { const resolver = pullResolvers.shift(); if (resolver) { resolver[0]({ done: false, value: payload }); } else { bufferedEvents.push(payload); if (bufferedEvents.length > maxBufferedEvents) { bufferedEvents.shift(); } } }); const abortListener = (event2) => { unsubscribe(); pullResolvers.forEach((resolver) => resolver[1](event2.target.reason)); pullResolvers.length = 0; bufferedEvents.length = 0; }; signal?.addEventListener("abort", abortListener, { once: true }); return new AsyncIteratorClass(async () => { if (signal?.aborted) { throw signal.reason; } if (bufferedEvents.length > 0) { return { done: false, value: bufferedEvents.shift() }; } return new Promise((resolve, reject) => { pullResolvers.push([resolve, reject]); }); }, async () => { unsubscribe(); signal?.removeEventListener("abort", abortListener); pullResolvers.forEach((resolver) => resolver[0]({ done: true, value: void 0 })); pullResolvers.length = 0; bufferedEvents.length = 0; }); } } class SequentialIdGenerator { nextId = 0; generate() { if (this.nextId === Number.MAX_SAFE_INTEGER) { this.nextId = 0; return Number.MAX_SAFE_INTEGER; } return this.nextId++; } } function onStart(callback) { return async (options, ...rest) => { await callback(options, ...rest); return await options.next(); }; } function onSuccess(callback) { return async (options, ...rest) => { const result = await options.next(); await callback(result, options, ...rest); return result; }; } function onError(callback) { return async (options, ...rest) => { try { return await options.next(); } catch (error) { await callback(error, options, ...rest); throw error; } }; } function onFinish(callback) { let state; return async (options, ...rest) => { try { const result = await options.next(); state = [null, result, true]; return result; } catch (error) { state = [error, void 0, false]; throw error; } finally { await callback(state, options, ...rest); } }; } function intercept(interceptors, options, main) { const next = (options2, index) => { const interceptor = interceptors[index]; if (!interceptor) { return main(options2); } return interceptor({ ...options2, next: (newOptions = options2) => next(newOptions, index + 1) }); }; return next(options, 0); } function parseEmptyableJSON(text) { if (!text) { return void 0; } return JSON.parse(text); } function stringifyJSON(value) { return JSON.stringify(value); } function findDeepMatches(check, payload, segments = [], maps = [], values = []) { if (check(payload)) { maps.push(segments); values.push(payload); } else if (Array.isArray(payload)) { payload.forEach((v, i) => { findDeepMatches(check, v, [...segments, i], maps, values); }); } else if (isObject(payload)) { for (const key in payload) { findDeepMatches(check, payload[key], [...segments, key], maps, values); } } return { maps, values }; } function isObject(value) { if (!value || typeof value !== "object") { return false; } const proto = Object.getPrototypeOf(value); return proto === Object.prototype || !proto || !proto.constructor; } function isTypescriptObject(value) { return !!value && (typeof value === "object" || typeof value === "function"); } function clone(value) { if (Array.isArray(value)) { return value.map(clone); } if (isObject(value)) { const result = {}; for (const key in value) { result[key] = clone(value[key]); } return result; } return value; } function get(object, path) { let current = object; for (const key of path) { if (!isTypescriptObject(current)) { return void 0; } current = current[key]; } return current; } function isPropertyKey(value) { const type = typeof value; return type === "string" || type === "number" || type === "symbol"; } const NullProtoObj = /* @__PURE__ */ (() => { const e = function() { }; e.prototype = /* @__PURE__ */ Object.create(null); Object.freeze(e.prototype); return e; })(); function value(value2, ...args) { if (typeof value2 === "function") { return value2(...args); } return value2; } export { AsyncIdQueue, AsyncIteratorClass, EventPublisher, NullProtoObj, SequentialIdGenerator, clone, defer, findDeepMatches, get, intercept, isAsyncIteratorObject, isObject, isPropertyKey, isTypescriptObject, onError, onFinish, onStart, onSuccess, once, parseEmptyableJSON, replicateAsyncIterator, resolveMaybeOptionalOptions, sequential, splitInHalf, stringifyJSON, toArray, value };