UNPKG

@orpc/shared

Version:

<div align="center"> <image align="center" src="https://orpc.dev/logo.webp" width=280 alt="oRPC logo" /> </div>

712 lines (692 loc) • 19.6 kB
export { group, guard, mapEntries, mapValues, omit, retry, sleep } 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 readAsBuffer(source) { if (typeof source.bytes === "function") { return source.bytes(); } return source.arrayBuffer(); } const ORPC_NAME = "orpc"; const ORPC_SHARED_PACKAGE_NAME = "@orpc/shared"; const ORPC_SHARED_PACKAGE_VERSION = "1.14.4"; class AbortError extends Error { constructor(...rest) { super(...rest); this.name = "AbortError"; } } 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))); } } const SPAN_ERROR_STATUS = 2; const GLOBAL_OTEL_CONFIG_KEY = `__${ORPC_SHARED_PACKAGE_NAME}@${ORPC_SHARED_PACKAGE_VERSION}/otel/config__`; function setGlobalOtelConfig(config) { globalThis[GLOBAL_OTEL_CONFIG_KEY] = config; } function getGlobalOtelConfig() { return globalThis[GLOBAL_OTEL_CONFIG_KEY]; } function startSpan(name, options = {}, context) { const tracer = getGlobalOtelConfig()?.tracer; return tracer?.startSpan(name, options, context); } function setSpanError(span, error, options = {}) { if (!span) { return; } const exception = toOtelException(error); span.recordException(exception); if (!options.signal?.aborted || options.signal.reason !== error) { span.setStatus({ code: SPAN_ERROR_STATUS, message: exception.message }); } } function setSpanAttribute(span, key, value) { if (!span || value === void 0) { return; } span.setAttribute(key, value); } function toOtelException(error) { if (error instanceof Error) { const exception = { message: error.message, name: error.name, stack: error.stack }; if ("code" in error && (typeof error.code === "string" || typeof error.code === "number")) { exception.code = error.code; } return exception; } return { message: String(error) }; } function toSpanAttributeValue(data) { if (data === void 0) { return "undefined"; } try { return JSON.stringify(data, (_, value) => { if (typeof value === "bigint") { return value.toString(); } if (value instanceof Map || value instanceof Set) { return Array.from(value); } return value; }); } catch { return String(data); } } async function runWithSpan({ name, context, ...options }, fn) { const tracer = getGlobalOtelConfig()?.tracer; if (!tracer) { return fn(); } const callback = async (span) => { try { return await fn(span); } catch (e) { setSpanError(span, e, options); throw e; } finally { span.end(); } }; if (context) { return tracer.startActiveSpan(name, options, context, callback); } else { return tracer.startActiveSpan(name, options, callback); } } async function runInSpanContext(span, fn) { const otelConfig = getGlobalOtelConfig(); if (!span || !otelConfig) { return fn(); } const ctx = otelConfig.trace.setSpan(otelConfig.context.active(), span); return otelConfig.context.with(ctx, fn); } class AsyncIdQueue { openIds = /* @__PURE__ */ new Set(); queues = /* @__PURE__ */ new Map(); waiters = /* @__PURE__ */ new Map(); get length() { return this.openIds.size; } get waiterIds() { return Array.from(this.waiters.keys()); } hasBufferedItems(id) { return Boolean(this.queues.get(id)?.length); } open(id) { this.openIds.add(id); } isOpen(id) { return this.openIds.has(id); } push(id, item) { this.assertOpen(id); const pending = this.waiters.get(id); if (pending?.length) { pending.shift()[0](item); if (pending.length === 0) { this.waiters.delete(id); } } else { const items = this.queues.get(id); if (items) { items.push(item); } else { this.queues.set(id, [item]); } } } async pull(id) { this.assertOpen(id); const items = this.queues.get(id); if (items?.length) { const item = items.shift(); if (items.length === 0) { this.queues.delete(id); } return item; } return new Promise((resolve, reject) => { const waitingPulls = this.waiters.get(id); const pending = [resolve, reject]; if (waitingPulls) { waitingPulls.push(pending); } else { this.waiters.set(id, [pending]); } }); } close({ id, reason } = {}) { if (id === void 0) { this.waiters.forEach((pendingPulls, id2) => { const error2 = reason ?? new AbortError(`[AsyncIdQueue] Queue[${id2}] was closed or aborted while waiting for pulling.`); pendingPulls.forEach(([, reject]) => reject(error2)); }); this.waiters.clear(); this.openIds.clear(); this.queues.clear(); return; } const error = reason ?? new AbortError(`[AsyncIdQueue] Queue[${id}] was closed or aborted while waiting for pulling.`); this.waiters.get(id)?.forEach(([, reject]) => reject(error)); this.waiters.delete(id); this.openIds.delete(id); this.queues.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 "next" in maybe && typeof maybe.next === "function" && 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 ids = Array.from({ length: count }, (_, i) => i.toString()); let isSourceFinished = false; const start = once(async () => { try { while (true) { const item = await source.next(); ids.forEach((id) => { if (queue.isOpen(id)) { queue.push(id, { next: item }); } }); if (item.done) { break; } } } catch (error) { ids.forEach((id) => { if (queue.isOpen(id)) { queue.push(id, { error }); } }); } finally { isSourceFinished = true; } }); const replicated = ids.map((id) => { queue.open(id); return new AsyncIteratorClass( async () => { start(); const item = await queue.pull(id); if (item.next) { return item.next; } throw item.error; }, async (reason) => { queue.close({ id }); if (reason !== "next" && !queue.length && !isSourceFinished) { isSourceFinished = true; await source?.return?.(); } } ); }); return replicated; } function asyncIteratorWithSpan({ name, ...options }, iterator) { let span; return new AsyncIteratorClass( async () => { span ??= startSpan(name); try { const result = await runInSpanContext(span, () => iterator.next()); span?.addEvent(result.done ? "completed" : "yielded"); return result; } catch (err) { setSpanError(span, err, options); throw err; } }, async (reason) => { try { if (reason !== "next") { await runInSpanContext(span, () => iterator.return?.()); } } catch (err) { setSpanError(span, err, options); throw err; } finally { span?.end(); } } ); } 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 = []); } listeners.push(listenerOrOptions); return once(() => { listeners.splice(listeners.indexOf(listenerOrOptions), 1); if (listeners.length === 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 { index = BigInt(1); generate() { const id = this.index.toString(36); this.index++; return id; } } function compareSequentialIds(a, b) { if (a.length !== b.length) { return a.length - b.length; } return a < b ? -1 : a > b ? 1 : 0; } 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 getConstructor(value) { if (!isTypescriptObject(value)) { return null; } return Object.getPrototypeOf(value)?.constructor; } 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]); } for (const sym of Object.getOwnPropertySymbols(value)) { result[sym] = clone(value[sym]); } 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; } function fallback(value2, fallback2) { return value2 === void 0 ? fallback2 : value2; } function preventNativeAwait(target) { return new Proxy(target, { get(target2, prop, receiver) { const value2 = Reflect.get(target2, prop, receiver); if (prop !== "then" || typeof value2 !== "function") { return value2; } return new Proxy(value2, { apply(targetFn, thisArg, args) { if (args.length !== 2 || args.some((arg) => !isNativeFunction(arg))) { return Reflect.apply(targetFn, thisArg, args); } let shouldOmit = true; args[0].call(thisArg, preventNativeAwait(new Proxy(target2, { get: (target3, prop2, receiver2) => { if (shouldOmit && prop2 === "then") { shouldOmit = false; return void 0; } return Reflect.get(target3, prop2, receiver2); } }))); } }); } }); } const NATIVE_FUNCTION_REGEX = /^\s*function\s*\(\)\s*\{\s*\[native code\]\s*\}\s*$/; function isNativeFunction(fn) { return typeof fn === "function" && NATIVE_FUNCTION_REGEX.test(fn.toString()); } function overlayProxy(target, partial) { const proxy = new Proxy(typeof target === "function" ? partial : target, { get(_, prop) { const targetValue = prop in partial ? partial : value(target); const v = Reflect.get(targetValue, prop); return typeof v === "function" ? v.bind(targetValue) : v; }, has(_, prop) { return Reflect.has(partial, prop) || Reflect.has(value(target), prop); } }); return proxy; } function streamToAsyncIteratorClass(stream) { const reader = stream.getReader(); return new AsyncIteratorClass( async () => { return reader.read(); }, async () => { await reader.cancel(); } ); } function asyncIteratorToStream(iterator) { return new ReadableStream({ async pull(controller) { const { done, value } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } }, async cancel() { await iterator.return?.(); } }); } function asyncIteratorToUnproxiedDataStream(iterator) { return new ReadableStream({ async pull(controller) { const { done, value } = await iterator.next(); if (done) { controller.close(); } else { const unproxied = isObject(value) ? { ...value } : Array.isArray(value) ? value.map((i) => i) : value; controller.enqueue(unproxied); } }, async cancel() { await iterator.return?.(); } }); } function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); } catch { return value; } } export { AbortError, AsyncIdQueue, AsyncIteratorClass, EventPublisher, NullProtoObj, ORPC_NAME, ORPC_SHARED_PACKAGE_NAME, ORPC_SHARED_PACKAGE_VERSION, SequentialIdGenerator, asyncIteratorToStream, asyncIteratorToUnproxiedDataStream, asyncIteratorWithSpan, clone, compareSequentialIds, defer, fallback, findDeepMatches, get, getConstructor, getGlobalOtelConfig, intercept, isAsyncIteratorObject, isObject, isPropertyKey, isTypescriptObject, onError, onFinish, onStart, onSuccess, once, overlayProxy, parseEmptyableJSON, preventNativeAwait, readAsBuffer, replicateAsyncIterator, resolveMaybeOptionalOptions, runInSpanContext, runWithSpan, sequential, setGlobalOtelConfig, setSpanAttribute, setSpanError, splitInHalf, startSpan, streamToAsyncIteratorClass, stringifyJSON, toArray, toOtelException, toSpanAttributeValue, tryDecodeURIComponent, value };