@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
JavaScript
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 };