+114
-75
@@ -12,3 +12,3 @@ 'use strict'; | ||
| const random = Math.random.bind(Math); | ||
| function createBirpc(functions, options) { | ||
| function createBirpc($functions, options) { | ||
| const { | ||
@@ -26,21 +26,84 @@ post, | ||
| } = options; | ||
| const rpcPromiseMap = /* @__PURE__ */ new Map(); | ||
| let $closed = false; | ||
| const _rpcPromiseMap = /* @__PURE__ */ new Map(); | ||
| let _promiseInit; | ||
| let closed = false; | ||
| async function _call(method, args, event, optional) { | ||
| if ($closed) | ||
| throw new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
| const req = { m: method, a: args, t: TYPE_REQUEST }; | ||
| if (optional) | ||
| req.o = true; | ||
| const send = async (_req) => post(serialize(_req)); | ||
| if (event) { | ||
| await send(req); | ||
| return; | ||
| } | ||
| if (_promiseInit) { | ||
| try { | ||
| await _promiseInit; | ||
| } finally { | ||
| _promiseInit = void 0; | ||
| } | ||
| } | ||
| let { promise, resolve, reject } = createPromiseWithResolvers(); | ||
| const id = nanoid(); | ||
| req.i = id; | ||
| let timeoutId; | ||
| async function handler(newReq = req) { | ||
| if (timeout >= 0) { | ||
| timeoutId = setTimeout(() => { | ||
| try { | ||
| const handleResult = options.onTimeoutError?.(method, args); | ||
| if (handleResult !== true) | ||
| throw new Error(`[birpc] timeout on calling "${method}"`); | ||
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| _rpcPromiseMap.delete(id); | ||
| }, timeout); | ||
| if (typeof timeoutId === "object") | ||
| timeoutId = timeoutId.unref?.(); | ||
| } | ||
| _rpcPromiseMap.set(id, { resolve, reject, timeoutId, method }); | ||
| await send(newReq); | ||
| return promise; | ||
| } | ||
| try { | ||
| if (options.onRequest) | ||
| await options.onRequest(req, handler, resolve); | ||
| else | ||
| await handler(); | ||
| } catch (e) { | ||
| if (options.onGeneralError?.(e) !== true) | ||
| throw e; | ||
| return; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| _rpcPromiseMap.delete(id); | ||
| } | ||
| return promise; | ||
| } | ||
| const $call = (method, ...args) => _call(method, args, false); | ||
| const $callOptional = (method, ...args) => _call(method, args, false, true); | ||
| const $callEvent = (method, ...args) => _call(method, args, true); | ||
| const $callRaw = (options2) => _call(options2.method, options2.args, options2.event, options2.optional); | ||
| const builtinMethods = { | ||
| $call, | ||
| $callOptional, | ||
| $callEvent, | ||
| $callRaw, | ||
| $rejectPendingCalls, | ||
| get $closed() { | ||
| return $closed; | ||
| }, | ||
| $close, | ||
| $functions | ||
| }; | ||
| const rpc = new Proxy({}, { | ||
| get(_, method) { | ||
| if (method === "$functions") | ||
| return functions; | ||
| if (method === "$close") | ||
| return close; | ||
| if (method === "$rejectPendingCalls") { | ||
| return rejectPendingCalls; | ||
| } | ||
| if (method === "$closed") | ||
| return closed; | ||
| if (method === "then" && !eventNames.includes("then") && !("then" in functions)) | ||
| if (Object.prototype.hasOwnProperty.call(builtinMethods, method)) | ||
| return builtinMethods[method]; | ||
| if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) | ||
| return void 0; | ||
| const sendEvent = async (...args) => { | ||
| await post(serialize({ m: method, a: args, t: TYPE_REQUEST })); | ||
| }; | ||
| const sendEvent = (...args) => _call(method, args, true); | ||
| if (eventNames.includes(method)) { | ||
@@ -50,49 +113,3 @@ sendEvent.asEvent = sendEvent; | ||
| } | ||
| const sendCall = async (...args) => { | ||
| if (closed) | ||
| throw new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
| if (_promiseInit) { | ||
| try { | ||
| await _promiseInit; | ||
| } finally { | ||
| _promiseInit = void 0; | ||
| } | ||
| } | ||
| let { promise, resolve, reject } = createPromiseWithResolvers(); | ||
| const id = nanoid(); | ||
| let timeoutId; | ||
| const _req = { m: method, a: args, i: id, t: TYPE_REQUEST }; | ||
| async function handler(req = _req) { | ||
| if (timeout >= 0) { | ||
| timeoutId = setTimeout(() => { | ||
| try { | ||
| const handleResult = options.onTimeoutError?.(method, args); | ||
| if (handleResult !== true) | ||
| throw new Error(`[birpc] timeout on calling "${method}"`); | ||
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| rpcPromiseMap.delete(id); | ||
| }, timeout); | ||
| if (typeof timeoutId === "object") | ||
| timeoutId = timeoutId.unref?.(); | ||
| } | ||
| rpcPromiseMap.set(id, { resolve, reject, timeoutId, method }); | ||
| await post(serialize(req)); | ||
| return promise; | ||
| } | ||
| try { | ||
| if (options.onRequest) | ||
| await options.onRequest(_req, handler, resolve); | ||
| else | ||
| await handler(); | ||
| } catch (e) { | ||
| clearTimeout(timeoutId); | ||
| rpcPromiseMap.delete(id); | ||
| if (options.onGeneralError?.(e) !== true) | ||
| throw e; | ||
| return; | ||
| } | ||
| return promise; | ||
| }; | ||
| const sendCall = (...args) => _call(method, args, false); | ||
| sendCall.asEvent = sendEvent; | ||
@@ -102,5 +119,5 @@ return sendCall; | ||
| }); | ||
| function close(customError) { | ||
| closed = true; | ||
| rpcPromiseMap.forEach(({ reject, method }) => { | ||
| function $close(customError) { | ||
| $closed = true; | ||
| _rpcPromiseMap.forEach(({ reject, method }) => { | ||
| const error = new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
@@ -113,7 +130,7 @@ if (customError) { | ||
| }); | ||
| rpcPromiseMap.clear(); | ||
| _rpcPromiseMap.clear(); | ||
| off(onMessage); | ||
| } | ||
| function rejectPendingCalls(handler) { | ||
| const entries = Array.from(rpcPromiseMap.values()); | ||
| function $rejectPendingCalls(handler) { | ||
| const entries = Array.from(_rpcPromiseMap.values()); | ||
| const handlerResults = entries.map(({ method, reject }) => { | ||
@@ -125,3 +142,3 @@ if (!handler) { | ||
| }); | ||
| rpcPromiseMap.clear(); | ||
| _rpcPromiseMap.clear(); | ||
| return handlerResults; | ||
@@ -139,5 +156,7 @@ } | ||
| if (msg.t === TYPE_REQUEST) { | ||
| const { m: method, a: args } = msg; | ||
| const { m: method, a: args, o: optional } = msg; | ||
| let result, error; | ||
| const fn = await (resolver ? resolver(method, functions[method]) : functions[method]); | ||
| let fn = await (resolver ? resolver(method, $functions[method]) : $functions[method]); | ||
| if (optional) | ||
| fn ||= () => void 0; | ||
| if (!fn) { | ||
@@ -147,3 +166,3 @@ error = new Error(`[birpc] function "${method}" not found`); | ||
| try { | ||
| result = await fn.apply(bind === "rpc" ? rpc : functions, args); | ||
| result = await fn.apply(bind === "rpc" ? rpc : $functions, args); | ||
| } catch (e) { | ||
@@ -179,3 +198,3 @@ error = e; | ||
| const { i: ack, r: result, e: error } = msg; | ||
| const promise = rpcPromiseMap.get(ack); | ||
| const promise = _rpcPromiseMap.get(ack); | ||
| if (promise) { | ||
@@ -188,3 +207,3 @@ clearTimeout(promise.timeoutId); | ||
| } | ||
| rpcPromiseMap.delete(ack); | ||
| _rpcPromiseMap.delete(ack); | ||
| } | ||
@@ -209,4 +228,24 @@ } | ||
| const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s })); | ||
| function _boardcast(method, args, event, optional) { | ||
| const clients = getClients(); | ||
| return Promise.all(clients.map((c) => c.$callRaw({ method, args, event, optional }))); | ||
| } | ||
| function $call(method, ...args) { | ||
| return _boardcast(method, args, false); | ||
| } | ||
| function $callOptional(method, ...args) { | ||
| return _boardcast(method, args, false, true); | ||
| } | ||
| function $callEvent(method, ...args) { | ||
| return _boardcast(method, args, true); | ||
| } | ||
| const broadcastBuiltin = { | ||
| $call, | ||
| $callOptional, | ||
| $callEvent | ||
| }; | ||
| const broadcastProxy = new Proxy({}, { | ||
| get(_, method) { | ||
| if (Object.prototype.hasOwnProperty.call(broadcastBuiltin, method)) | ||
| return broadcastBuiltin[method]; | ||
| const client = getClients(); | ||
@@ -213,0 +252,0 @@ const callbacks = client.map((c) => c[method]); |
+62
-8
@@ -103,14 +103,64 @@ type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & { | ||
| interface BirpcReturnBuiltin<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
| /** | ||
| * Raw functions object | ||
| */ | ||
| $functions: LocalFunctions; | ||
| /** | ||
| * Whether the RPC is closed | ||
| */ | ||
| readonly $closed: boolean; | ||
| /** | ||
| * Close the RPC connection | ||
| */ | ||
| $close: (error?: Error) => void; | ||
| $closed: boolean; | ||
| /** | ||
| * Reject pending calls | ||
| */ | ||
| $rejectPendingCalls: (handler?: PendingCallHandler) => Promise<void>[]; | ||
| }; | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| /** | ||
| * Call the remote function with the raw options. | ||
| */ | ||
| $callRaw: (options: { | ||
| method: string; | ||
| args: unknown[]; | ||
| event?: boolean; | ||
| optional?: boolean; | ||
| }) => Promise<Awaited<ReturnType<any>>[]>; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & BirpcReturnBuiltin<RemoteFunctions, LocalFunctions>; | ||
| type PendingCallHandler = (options: Pick<PromiseEntry, 'method' | 'reject'>) => void | Promise<void>; | ||
| interface BirpcGroupReturnBuiltin<RemoteFunctions> { | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| } | ||
| type BirpcGroupReturn<RemoteFunctions> = { | ||
| [K in keyof RemoteFunctions]: BirpcGroupFn<RemoteFunctions[K]>; | ||
| }; | ||
| } & BirpcGroupReturnBuiltin<RemoteFunctions>; | ||
| interface BirpcGroup<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
@@ -146,9 +196,13 @@ readonly clients: BirpcReturn<RemoteFunctions, LocalFunctions>[]; | ||
| a: any[]; | ||
| /** | ||
| * Optional | ||
| */ | ||
| o?: boolean; | ||
| } | ||
| declare const DEFAULT_TIMEOUT = 60000; | ||
| declare const setTimeout: typeof globalThis.setTimeout; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>($functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function cachedMap<T, R>(items: T[], fn: ((i: T) => R)): R[]; | ||
| declare function createBirpcGroup<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, channels: ChannelOptions[] | (() => ChannelOptions[]), options?: EventOptions<RemoteFunctions>): BirpcGroup<RemoteFunctions, LocalFunctions>; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcOptions, type BirpcResolver, type BirpcReturn, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcGroupReturnBuiltin, type BirpcOptions, type BirpcResolver, type BirpcReturn, type BirpcReturnBuiltin, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; |
+62
-8
@@ -103,14 +103,64 @@ type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & { | ||
| interface BirpcReturnBuiltin<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
| /** | ||
| * Raw functions object | ||
| */ | ||
| $functions: LocalFunctions; | ||
| /** | ||
| * Whether the RPC is closed | ||
| */ | ||
| readonly $closed: boolean; | ||
| /** | ||
| * Close the RPC connection | ||
| */ | ||
| $close: (error?: Error) => void; | ||
| $closed: boolean; | ||
| /** | ||
| * Reject pending calls | ||
| */ | ||
| $rejectPendingCalls: (handler?: PendingCallHandler) => Promise<void>[]; | ||
| }; | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| /** | ||
| * Call the remote function with the raw options. | ||
| */ | ||
| $callRaw: (options: { | ||
| method: string; | ||
| args: unknown[]; | ||
| event?: boolean; | ||
| optional?: boolean; | ||
| }) => Promise<Awaited<ReturnType<any>>[]>; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & BirpcReturnBuiltin<RemoteFunctions, LocalFunctions>; | ||
| type PendingCallHandler = (options: Pick<PromiseEntry, 'method' | 'reject'>) => void | Promise<void>; | ||
| interface BirpcGroupReturnBuiltin<RemoteFunctions> { | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| } | ||
| type BirpcGroupReturn<RemoteFunctions> = { | ||
| [K in keyof RemoteFunctions]: BirpcGroupFn<RemoteFunctions[K]>; | ||
| }; | ||
| } & BirpcGroupReturnBuiltin<RemoteFunctions>; | ||
| interface BirpcGroup<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
@@ -146,9 +196,13 @@ readonly clients: BirpcReturn<RemoteFunctions, LocalFunctions>[]; | ||
| a: any[]; | ||
| /** | ||
| * Optional | ||
| */ | ||
| o?: boolean; | ||
| } | ||
| declare const DEFAULT_TIMEOUT = 60000; | ||
| declare const setTimeout: typeof globalThis.setTimeout; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>($functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function cachedMap<T, R>(items: T[], fn: ((i: T) => R)): R[]; | ||
| declare function createBirpcGroup<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, channels: ChannelOptions[] | (() => ChannelOptions[]), options?: EventOptions<RemoteFunctions>): BirpcGroup<RemoteFunctions, LocalFunctions>; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcOptions, type BirpcResolver, type BirpcReturn, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcGroupReturnBuiltin, type BirpcOptions, type BirpcResolver, type BirpcReturn, type BirpcReturnBuiltin, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; |
+62
-8
@@ -103,14 +103,64 @@ type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & { | ||
| interface BirpcReturnBuiltin<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
| /** | ||
| * Raw functions object | ||
| */ | ||
| $functions: LocalFunctions; | ||
| /** | ||
| * Whether the RPC is closed | ||
| */ | ||
| readonly $closed: boolean; | ||
| /** | ||
| * Close the RPC connection | ||
| */ | ||
| $close: (error?: Error) => void; | ||
| $closed: boolean; | ||
| /** | ||
| * Reject pending calls | ||
| */ | ||
| $rejectPendingCalls: (handler?: PendingCallHandler) => Promise<void>[]; | ||
| }; | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| /** | ||
| * Call the remote function with the raw options. | ||
| */ | ||
| $callRaw: (options: { | ||
| method: string; | ||
| args: unknown[]; | ||
| event?: boolean; | ||
| optional?: boolean; | ||
| }) => Promise<Awaited<ReturnType<any>>[]>; | ||
| } | ||
| type BirpcReturn<RemoteFunctions, LocalFunctions = Record<string, never>> = { | ||
| [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; | ||
| } & BirpcReturnBuiltin<RemoteFunctions, LocalFunctions>; | ||
| type PendingCallHandler = (options: Pick<PromiseEntry, 'method' | 'reject'>) => void | Promise<void>; | ||
| interface BirpcGroupReturnBuiltin<RemoteFunctions> { | ||
| /** | ||
| * Call the remote function and wait for the result. | ||
| * An alternative to directly calling the function | ||
| */ | ||
| $call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>; | ||
| /** | ||
| * Same as `$call`, but returns `undefined` if the function is not defined on the remote side. | ||
| */ | ||
| $callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>; | ||
| /** | ||
| * Send event without asking for response | ||
| */ | ||
| $callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>; | ||
| } | ||
| type BirpcGroupReturn<RemoteFunctions> = { | ||
| [K in keyof RemoteFunctions]: BirpcGroupFn<RemoteFunctions[K]>; | ||
| }; | ||
| } & BirpcGroupReturnBuiltin<RemoteFunctions>; | ||
| interface BirpcGroup<RemoteFunctions, LocalFunctions = Record<string, never>> { | ||
@@ -146,9 +196,13 @@ readonly clients: BirpcReturn<RemoteFunctions, LocalFunctions>[]; | ||
| a: any[]; | ||
| /** | ||
| * Optional | ||
| */ | ||
| o?: boolean; | ||
| } | ||
| declare const DEFAULT_TIMEOUT = 60000; | ||
| declare const setTimeout: typeof globalThis.setTimeout; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function createBirpc<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>($functions: LocalFunctions, options: BirpcOptions<RemoteFunctions>): BirpcReturn<RemoteFunctions, LocalFunctions>; | ||
| declare function cachedMap<T, R>(items: T[], fn: ((i: T) => R)): R[]; | ||
| declare function createBirpcGroup<RemoteFunctions = Record<string, never>, LocalFunctions extends object = Record<string, never>>(functions: LocalFunctions, channels: ChannelOptions[] | (() => ChannelOptions[]), options?: EventOptions<RemoteFunctions>): BirpcGroup<RemoteFunctions, LocalFunctions>; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcOptions, type BirpcResolver, type BirpcReturn, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; | ||
| export { type ArgumentsType, type BirpcFn, type BirpcGroup, type BirpcGroupFn, type BirpcGroupReturn, type BirpcGroupReturnBuiltin, type BirpcOptions, type BirpcResolver, type BirpcReturn, type BirpcReturnBuiltin, type ChannelOptions, DEFAULT_TIMEOUT, type EventOptions, type PromisifyFn, type ReturnType, type Thenable, cachedMap, createBirpc, createBirpcGroup }; |
+114
-75
@@ -10,3 +10,3 @@ const TYPE_REQUEST = "q"; | ||
| const random = Math.random.bind(Math); | ||
| function createBirpc(functions, options) { | ||
| function createBirpc($functions, options) { | ||
| const { | ||
@@ -24,21 +24,84 @@ post, | ||
| } = options; | ||
| const rpcPromiseMap = /* @__PURE__ */ new Map(); | ||
| let $closed = false; | ||
| const _rpcPromiseMap = /* @__PURE__ */ new Map(); | ||
| let _promiseInit; | ||
| let closed = false; | ||
| async function _call(method, args, event, optional) { | ||
| if ($closed) | ||
| throw new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
| const req = { m: method, a: args, t: TYPE_REQUEST }; | ||
| if (optional) | ||
| req.o = true; | ||
| const send = async (_req) => post(serialize(_req)); | ||
| if (event) { | ||
| await send(req); | ||
| return; | ||
| } | ||
| if (_promiseInit) { | ||
| try { | ||
| await _promiseInit; | ||
| } finally { | ||
| _promiseInit = void 0; | ||
| } | ||
| } | ||
| let { promise, resolve, reject } = createPromiseWithResolvers(); | ||
| const id = nanoid(); | ||
| req.i = id; | ||
| let timeoutId; | ||
| async function handler(newReq = req) { | ||
| if (timeout >= 0) { | ||
| timeoutId = setTimeout(() => { | ||
| try { | ||
| const handleResult = options.onTimeoutError?.(method, args); | ||
| if (handleResult !== true) | ||
| throw new Error(`[birpc] timeout on calling "${method}"`); | ||
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| _rpcPromiseMap.delete(id); | ||
| }, timeout); | ||
| if (typeof timeoutId === "object") | ||
| timeoutId = timeoutId.unref?.(); | ||
| } | ||
| _rpcPromiseMap.set(id, { resolve, reject, timeoutId, method }); | ||
| await send(newReq); | ||
| return promise; | ||
| } | ||
| try { | ||
| if (options.onRequest) | ||
| await options.onRequest(req, handler, resolve); | ||
| else | ||
| await handler(); | ||
| } catch (e) { | ||
| if (options.onGeneralError?.(e) !== true) | ||
| throw e; | ||
| return; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| _rpcPromiseMap.delete(id); | ||
| } | ||
| return promise; | ||
| } | ||
| const $call = (method, ...args) => _call(method, args, false); | ||
| const $callOptional = (method, ...args) => _call(method, args, false, true); | ||
| const $callEvent = (method, ...args) => _call(method, args, true); | ||
| const $callRaw = (options2) => _call(options2.method, options2.args, options2.event, options2.optional); | ||
| const builtinMethods = { | ||
| $call, | ||
| $callOptional, | ||
| $callEvent, | ||
| $callRaw, | ||
| $rejectPendingCalls, | ||
| get $closed() { | ||
| return $closed; | ||
| }, | ||
| $close, | ||
| $functions | ||
| }; | ||
| const rpc = new Proxy({}, { | ||
| get(_, method) { | ||
| if (method === "$functions") | ||
| return functions; | ||
| if (method === "$close") | ||
| return close; | ||
| if (method === "$rejectPendingCalls") { | ||
| return rejectPendingCalls; | ||
| } | ||
| if (method === "$closed") | ||
| return closed; | ||
| if (method === "then" && !eventNames.includes("then") && !("then" in functions)) | ||
| if (Object.prototype.hasOwnProperty.call(builtinMethods, method)) | ||
| return builtinMethods[method]; | ||
| if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) | ||
| return void 0; | ||
| const sendEvent = async (...args) => { | ||
| await post(serialize({ m: method, a: args, t: TYPE_REQUEST })); | ||
| }; | ||
| const sendEvent = (...args) => _call(method, args, true); | ||
| if (eventNames.includes(method)) { | ||
@@ -48,49 +111,3 @@ sendEvent.asEvent = sendEvent; | ||
| } | ||
| const sendCall = async (...args) => { | ||
| if (closed) | ||
| throw new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
| if (_promiseInit) { | ||
| try { | ||
| await _promiseInit; | ||
| } finally { | ||
| _promiseInit = void 0; | ||
| } | ||
| } | ||
| let { promise, resolve, reject } = createPromiseWithResolvers(); | ||
| const id = nanoid(); | ||
| let timeoutId; | ||
| const _req = { m: method, a: args, i: id, t: TYPE_REQUEST }; | ||
| async function handler(req = _req) { | ||
| if (timeout >= 0) { | ||
| timeoutId = setTimeout(() => { | ||
| try { | ||
| const handleResult = options.onTimeoutError?.(method, args); | ||
| if (handleResult !== true) | ||
| throw new Error(`[birpc] timeout on calling "${method}"`); | ||
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| rpcPromiseMap.delete(id); | ||
| }, timeout); | ||
| if (typeof timeoutId === "object") | ||
| timeoutId = timeoutId.unref?.(); | ||
| } | ||
| rpcPromiseMap.set(id, { resolve, reject, timeoutId, method }); | ||
| await post(serialize(req)); | ||
| return promise; | ||
| } | ||
| try { | ||
| if (options.onRequest) | ||
| await options.onRequest(_req, handler, resolve); | ||
| else | ||
| await handler(); | ||
| } catch (e) { | ||
| clearTimeout(timeoutId); | ||
| rpcPromiseMap.delete(id); | ||
| if (options.onGeneralError?.(e) !== true) | ||
| throw e; | ||
| return; | ||
| } | ||
| return promise; | ||
| }; | ||
| const sendCall = (...args) => _call(method, args, false); | ||
| sendCall.asEvent = sendEvent; | ||
@@ -100,5 +117,5 @@ return sendCall; | ||
| }); | ||
| function close(customError) { | ||
| closed = true; | ||
| rpcPromiseMap.forEach(({ reject, method }) => { | ||
| function $close(customError) { | ||
| $closed = true; | ||
| _rpcPromiseMap.forEach(({ reject, method }) => { | ||
| const error = new Error(`[birpc] rpc is closed, cannot call "${method}"`); | ||
@@ -111,7 +128,7 @@ if (customError) { | ||
| }); | ||
| rpcPromiseMap.clear(); | ||
| _rpcPromiseMap.clear(); | ||
| off(onMessage); | ||
| } | ||
| function rejectPendingCalls(handler) { | ||
| const entries = Array.from(rpcPromiseMap.values()); | ||
| function $rejectPendingCalls(handler) { | ||
| const entries = Array.from(_rpcPromiseMap.values()); | ||
| const handlerResults = entries.map(({ method, reject }) => { | ||
@@ -123,3 +140,3 @@ if (!handler) { | ||
| }); | ||
| rpcPromiseMap.clear(); | ||
| _rpcPromiseMap.clear(); | ||
| return handlerResults; | ||
@@ -137,5 +154,7 @@ } | ||
| if (msg.t === TYPE_REQUEST) { | ||
| const { m: method, a: args } = msg; | ||
| const { m: method, a: args, o: optional } = msg; | ||
| let result, error; | ||
| const fn = await (resolver ? resolver(method, functions[method]) : functions[method]); | ||
| let fn = await (resolver ? resolver(method, $functions[method]) : $functions[method]); | ||
| if (optional) | ||
| fn ||= () => void 0; | ||
| if (!fn) { | ||
@@ -145,3 +164,3 @@ error = new Error(`[birpc] function "${method}" not found`); | ||
| try { | ||
| result = await fn.apply(bind === "rpc" ? rpc : functions, args); | ||
| result = await fn.apply(bind === "rpc" ? rpc : $functions, args); | ||
| } catch (e) { | ||
@@ -177,3 +196,3 @@ error = e; | ||
| const { i: ack, r: result, e: error } = msg; | ||
| const promise = rpcPromiseMap.get(ack); | ||
| const promise = _rpcPromiseMap.get(ack); | ||
| if (promise) { | ||
@@ -186,3 +205,3 @@ clearTimeout(promise.timeoutId); | ||
| } | ||
| rpcPromiseMap.delete(ack); | ||
| _rpcPromiseMap.delete(ack); | ||
| } | ||
@@ -207,4 +226,24 @@ } | ||
| const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s })); | ||
| function _boardcast(method, args, event, optional) { | ||
| const clients = getClients(); | ||
| return Promise.all(clients.map((c) => c.$callRaw({ method, args, event, optional }))); | ||
| } | ||
| function $call(method, ...args) { | ||
| return _boardcast(method, args, false); | ||
| } | ||
| function $callOptional(method, ...args) { | ||
| return _boardcast(method, args, false, true); | ||
| } | ||
| function $callEvent(method, ...args) { | ||
| return _boardcast(method, args, true); | ||
| } | ||
| const broadcastBuiltin = { | ||
| $call, | ||
| $callOptional, | ||
| $callEvent | ||
| }; | ||
| const broadcastProxy = new Proxy({}, { | ||
| get(_, method) { | ||
| if (Object.prototype.hasOwnProperty.call(broadcastBuiltin, method)) | ||
| return broadcastBuiltin[method]; | ||
| const client = getClients(); | ||
@@ -211,0 +250,0 @@ const callbacks = client.map((c) => c[method]); |
+2
-1
| { | ||
| "name": "birpc", | ||
| "type": "module", | ||
| "version": "2.7.0", | ||
| "version": "2.8.0", | ||
| "description": "Message based Two-way remote procedure call", | ||
@@ -39,2 +39,3 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
| "@types/node": "^22.13.13", | ||
| "@vitest/coverage-v8": "3.0.9", | ||
| "bumpp": "^10.1.0", | ||
@@ -41,0 +42,0 @@ "eslint": "^9.23.0", |
47416
23.71%783
20.28%11
10%