Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

async-call-rpc

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

async-call-rpc - npm Package Compare versions

Comparing version 6.3.1 to 6.4.0

src/utils/encoder.ts

366

out/base.d.ts
/**
* AbortSignal
* @public
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* This is a subset of the AbortSignal interface defined in the [WinterCG](https://wintercg.org/).
*/
export declare interface AbortSignalLike {
readonly aborted: boolean;
addEventListener(type: 'abort', listener: () => void, options: {
once: boolean;
}): void;
removeEventListener(type: 'abort', listener: () => void): void;
throwIfAborted(): void;
reason: any;
}
/**
* Create a RPC server & client.

@@ -22,15 +39,20 @@ *

/**
* Log options of AsyncCall
* Log options
* @remarks
* This option controls how AsyncCall should log RPC calls to the console.
* This option controls how AsyncCall log requests to the console.
* @public
* @privateRemarks
* TODO: rename to AsyncCallLogOptions
* TODO: split to server log and client log
*/
export declare interface AsyncCallLogLevel {
/**
* Log all requests to this instance
* Log all incoming requests
* @defaultValue true
* @privateRemarks
* TODO: rename to called
*/
beCalled?: boolean;
/**
* Log all errors produced when responding requests
* Log all errors when responding requests
* @defaultValue true

@@ -40,3 +62,3 @@ */

/**
* Log remote errors
* Log errors from the remote
* @defaultValue true

@@ -46,10 +68,12 @@ */

/**
* Send the call stack to the remote when making requests
* Send the stack to the remote when making requests
* @defaultValue false
* @privateRemarks
* TODO: rename this field to sendRequestStack and move it to AsyncCallOptions.
*/
sendLocalStack?: boolean;
/**
* Control if AsyncCall should make the log better
* Style of the log
* @remarks
* If use "pretty", it will call the logger with some CSS to make the log easier to read.
* If this option is set to "pretty", it will log with some CSS to make the log easier to read in the browser devtools.
* Check out this article to know more about it: {@link https://dev.to/annlin/consolelog-with-css-style-1mmp | Console.log with CSS Style}

@@ -60,5 +84,5 @@ * @defaultValue 'pretty'

/**
* Log a function that allows to execute the request with same arguments again
* If log a function that can replay the request
* @remarks
* Do not use this options in the production environment because it will log a closure that captures the arguments of requests. This may cause memory leak.
* Do not use this options in the production environment because it will log a closure that captures all arguments of requests. This may cause memory leak.
* @defaultValue false

@@ -73,29 +97,36 @@ */

*/
export declare interface AsyncCallOptions {
export declare interface AsyncCallOptions<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* This option is used for better log print.
* Name used when pretty log is enabled.
* @defaultValue `rpc`
* @deprecated Renamed to "name".
*/
key?: string;
/**
* How to serialize and deserialize the JSON RPC payload
*
* Name used when pretty log is enabled.
* @defaultValue `rpc`
*/
name?: string;
/**
* Serializer of the requests and responses.
* @deprecated Use "encoding" option instead. This option will be removed in the next major version.
* @see {@link Serialization}.
*/
serializer?: Serialization;
/**
* Encoder of requests and responses.
* @see {@link IsomorphicEncoder} or {@link IsomorphicEncoderFull}.
* @remarks
* See {@link Serialization}.
* There is some built-in serializer:
* There are some built-in encoders:
*
* - {@link NoSerialization} (Not doing anything to the message)
*
* - {@link JSONSerialization} (Using JSON.parse/stringify in the backend)
*
* - {@link https://github.com/jack-works/async-call-rpc#web-deno-and-node-bson | BSONSerialization} (use the {@link https://npmjs.org/bson | bson} as the serializer)
*
* @defaultValue {@link NoSerialization}
* - JSONEncoder: is using JSON.parser and JSON.stringify under the hood.
* @defaultValue undefined
*/
serializer?: Serialization;
encoder?: IsomorphicEncoder<EncodedRequest, EncodedResponse> | IsomorphicEncoderFull<EncodedRequest, EncodedResponse>;
/**
* Provide the logger of AsyncCall
* @remarks
* See {@link ConsoleInterface}
* Provide the logger
* @see {@link ConsoleInterface}
* @defaultValue globalThis.console
* @privateRemarks
* TODO: allow post-create tweak?
*/

@@ -107,8 +138,10 @@ logger?: ConsoleInterface;

* {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/websocket.client.ts | Example for CallbackBasedChannel} or {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/websocket.server.ts | Example for EventBasedChannel}
* @privateRemarks
* TODO: split to ClientChannel (onResponse, send) and IsomorphicChannel
*/
channel: CallbackBasedChannel | EventBasedChannel | Promise<CallbackBasedChannel | EventBasedChannel>;
channel: CallbackBasedChannel<EncodedRequest | EncodedResponse> | EventBasedChannel<EncodedRequest | EncodedResponse> | Promise<CallbackBasedChannel<EncodedRequest | EncodedResponse> | EventBasedChannel<EncodedRequest | EncodedResponse>>;
/**
* Choose log level.
* @remarks
* - `true` is a reasonable default value, which means all options are the default options in the {@link AsyncCallLogLevel}
* - `true` is a reasonable default value, which means all options are the default in the {@link AsyncCallLogLevel}
*

@@ -119,6 +152,9 @@ * - `false` is disable all logs

* @defaultValue true
* @privateRemarks
* TODO: allow post-create tweak?
*/
log?: AsyncCallLogLevel | boolean | 'all';
/**
* Control the behavior that different from the JSON RPC spec. See {@link AsyncCallStrictJSONRPC}
* Control the behavior that different from the JSON-RPC spec
* @see {@link AsyncCallStrictJSONRPC}
* @remarks

@@ -132,13 +168,22 @@ * - `true` is to enable all strict options

* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* See {@link https://www.jsonrpc.org/specification#parameter_structures}
* @deprecated renamed to "parameterStructure"
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name';
/**
* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* When using `by-name`, only first parameter of the requests are sent to the remote and it must be an object.
*
* @privateRemarks
* TODO: review the edge cases when using "by-name".
* TODO: throw an error/print a warning when using "by-name" and the first parameter is not an object/more than 1 parameter is given.
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name';
parameterStructure?: 'by-position' | 'by-name';
/**

@@ -157,16 +202,18 @@ * Prefer local implementation than remote.

/**
* Control how to report error response according to the exception
* Change the {@link ErrorResponseDetail}.
* @privateRemarks
* TODO: provide a JSONRPCError class to allow customizing ErrorResponseDetail without mapError.
*/
mapError?: ErrorMapFunction<unknown>;
/**
* If the instance should be "thenable".
* If the instance is "thenable".
* @defaultValue undefined
* @remarks
* If this options is set to `true`, it will return a `then` method normally (forwards the call to the remote).
* If this options is set to `true`, it will return a `then` method normally (forwards the request to the remote).
*
* If this options is set to `false`, it will return `undefined` even the remote has a method called "then".
* If this options is set to `false`, it will return `undefined`, which means a method named "then" on the remote is not reachable.
*
* If this options is set to `undefined`, it will return `undefined` and show a warning. You must explicitly set this option to `true` or `false` to dismiss the warning.
*
* The motivation of this option is to resolve the problem caused by Promise auto-unwrapping.
* This option is used to resolve the problem caused by Promise auto-unwrapping.
*

@@ -186,13 +233,39 @@ * Consider this code:

thenable?: boolean;
/**
* AbortSignal to stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, except for the pending ones.
*/
signal?: AbortSignalLike;
/**
* AbortSignal to force stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, and the pending requests will be forcibly rejected and pending results will be ignored.
*/
forceSignal?: AbortSignalLike;
}
/**
* Control the behavior that different from the JSON RPC spec.
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
* @deprecated renamed to {@link AsyncCallStrictOptions}
*/
export declare interface AsyncCallStrictJSONRPC {
export declare interface AsyncCallStrictJSONRPC extends AsyncCallStrictOptions {
}
/**
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
*/
export declare interface AsyncCallStrictOptions {
/**
* Controls if AsyncCall send an ErrorResponse when the requested method is not defined.
* @remarks
* Set this options to false, AsyncCall will ignore the request (but print a log) if the method is not defined.
* If this option is set to false, AsyncCall will ignore the request and print a log if the method is not defined.
* @defaultValue true

@@ -204,3 +277,4 @@ */

* @remarks
* Set this options to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload. This is useful when the message channel is also used to transfer other kinds of messages.
* If this option is set to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload.
* This is useful when the message channel is also used to transfer other kinds of messages.
* @defaultValue true

@@ -212,3 +286,3 @@ */

/**
* Make all function in the type T becomes async functions and filtering non-Functions out.
* Make all functions in T becomes an async function and filter non-Functions out.
*

@@ -262,6 +336,23 @@ * @remarks

*/
setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown) => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown) => boolean | Promise<boolean>): (() => void) | void;
setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown, hint?: undefined | 'request' | 'response') => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown, hint?: undefined | 'request' | 'response') => boolean | Promise<boolean>): (() => void) | void;
}
/**
* Encoder of the client.
* @public
*/
export declare interface ClientEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request object.
* @param data - The request object
*/
encodeRequest(data: Requests): EncodedRequest | PromiseLike<EncodedRequest>;
/**
* Decode the response object.
* @param encoded - The encoded response object
*/
decodeResponse(encoded: EncodedResponse): Responses | PromiseLike<Responses>;
}
/**
* The minimal Console interface that AsyncCall needs.

@@ -287,4 +378,6 @@ * @public

* @param request - The request object
* @privateRemarks
* TODO: remove T generic parameter
*/
export declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<JSONRPCRequest>) => {
export declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<Request>) => {
code: number;

@@ -296,6 +389,24 @@ message: string;

/**
* ! This file MUST NOT contain any import statement.
* ! This file is part of public API of this package (for Deno users).
* JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export declare interface ErrorResponse<Error = unknown> {
readonly jsonrpc: '2.0';
readonly id?: ID;
readonly error: ErrorResponseDetail<Error>;
}
/**
* The "error" record on the JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export declare interface ErrorResponseDetail<Error = unknown> {
readonly code: number;
readonly message: string;
readonly data?: Error;
}
/**
* This interface represents a "on message" - "send response" model.

@@ -313,3 +424,3 @@ * @remarks

*/
on(listener: (data: Data) => void): void | (() => void);
on(listener: (data: Data, hint?: 'request' | 'response' | undefined) => void): void | (() => void);
/**

@@ -319,6 +430,12 @@ * Send the data to the remote side.

*/
send(data: Data): void;
send(data: Data): void | Promise<void>;
}
/**
* ID of a JSON-RPC request/response.
* @public
*/
export declare type ID = string | number | null | undefined;
/**
* Make the returning type to `Promise<void>`

@@ -336,13 +453,67 @@ * @internal

/**
* JSON RPC Request object
* Encoder that work for both server and client.
* @public
*/
export declare type JSONRPCRequest = {
jsonrpc: '2.0';
id?: string | number | null;
method: string;
params: readonly unknown[] | object;
};
export declare interface IsomorphicEncoder<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request or response object.
* @param data - The request or response object
*/
encode(data: Requests | Responses): EncodedRequest | EncodedResponse | PromiseLike<EncodedRequest | EncodedResponse>;
/**
* Decode the request or response object.
* @param encoded - The encoded request or response object
*/
decode(encoded: EncodedRequest | EncodedResponse): Requests | Responses | PromiseLike<Requests | Responses>;
}
/**
* Encoder that work for both server and client.
* @public
*/
export declare interface IsomorphicEncoderFull<EncodedRequest = unknown, EncodedResponse = unknown> extends ClientEncoding<EncodedRequest, EncodedResponse>, ServerEncoding<EncodedRequest, EncodedResponse>, Partial<Pick<IsomorphicEncoder, 'decode'>> {
}
/**
* Create a encoder by JSON.parse/stringify
*
* @public
* @param options - Options for this encoder.
* @remarks {@link IsomorphicEncoder}
*/
export declare function JSONEncoder({ keepUndefined, replacer, reviver, space, }?: JSONEncoderOptions): IsomorphicEncoder;
/** @public */
export declare namespace JSONEncoder {
const Default: IsomorphicEncoder<unknown, unknown>;
}
/**
* @public
* Options of {@link (JSONEncoder:function)}
*/
export declare interface JSONEncoderOptions {
/** Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */
space?: string | number | undefined;
/**
* How to handle `"undefined"` in the result of {@link SuccessResponse}.
*
* @remarks
* If you need a full support of encoding `undefined`, for example, when the result is `{ field: undefined }` and you want to keep it,
* you need to find another library to do this.
*
* If this is not handled properly, `JSON.stringify` will emit an invalid JSON-RPC object (fields with `undefined` value will be omitted).
*
* Options:
* - `"null"`(**default**): convert it to `null`.
* - `false`: do not do anything, let it break.
*/
keepUndefined?: false | 'null' | undefined;
/** A function that transforms the results. */
replacer?: ((this: any, key: string, value: any) => any) | undefined;
/** A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. */
reviver?: ((this: any, key: string, value: any) => any) | undefined;
}
/**
* Create a serialization by JSON.parse/stringify

@@ -383,5 +554,41 @@ *

/**
* Serialize and deserialize of the JSON RPC payload
* JSON-RPC Request object.
* @public
* @see https://www.jsonrpc.org/specification#request_object
*/
export declare interface Request {
readonly jsonrpc: '2.0';
readonly id?: ID;
readonly method: string;
readonly params: readonly unknown[] | object;
/**
* Non-standard field. It records the caller's stack of this Request.
*/
readonly remoteStack?: string | undefined;
}
/**
* A request object or an array of request objects.
* @public
*/
export declare type Requests = Request | readonly Request[];
/**
* A JSON-RPC response object
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export declare type Response = SuccessResponse | ErrorResponse;
/**
* A response object or an array of response objects.
* @public
*/
export declare type Responses = Response | readonly Response[];
/**
* Serialize and deserialize of the JSON-RPC payload
* @public
* @deprecated Use {@link IsomorphicEncoder} instead.
*/
export declare interface Serialization {

@@ -400,2 +607,45 @@ /**

/**
* Encoder of the server.
* @public
*/
export declare interface ServerEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the response object.
* @param data - The response object
*/
decodeRequest(encoded: EncodedRequest): Requests | PromiseLike<Requests>;
/**
* Decode the request object.
* @param encoded - The encoded request object
*/
encodeResponse(data: Responses): EncodedResponse | PromiseLike<EncodedResponse>;
}
/**
* JSON-RPC SuccessResponse object.
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export declare interface SuccessResponse {
readonly jsonrpc: '2.0';
readonly id?: ID;
result: unknown;
/**
* Non-standard property
* @remarks
* This is a non-standard field that used to represent the result field should be `undefined` instead of `null`.
*
* A field with value `undefined` will be omitted in `JSON.stringify`,
* and if the `"result"` field is omitted, this is no longer a valid JSON-RPC response object.
*
* By default, AsyncCall will convert `undefined` to `null` to keep the response valid, but it _won't_ add this field.
*
* Set `keepUndefined` in JSONEncoderOptions to `"keep"` will add this field.
*
* This field starts with a space, so TypeScript will hide it when providing completion.
*/
undef?: unknown;
}
export { }

454

out/base.js

@@ -8,2 +8,170 @@ /// <reference types="./base.d.ts" />

const isString = (x)=>typeof x === 'string';
const isBoolean = (x)=>typeof x === 'boolean';
const isFunction = (x)=>typeof x === 'function';
const isObject = (params)=>typeof params === 'object' && params !== null;
const ERROR = 'Error';
const undefined$1 = void 0;
const Promise_resolve = (x)=>Promise.resolve(x);
const { isArray } = Array;
const { apply } = Reflect;
/**
* Serialization implementation that do nothing
* @remarks {@link Serialization}
* @public
* @deprecated Will be removed in next major version
*/ const NoSerialization = {
serialization (from) {
return from;
},
deserialization (serialized) {
return serialized;
}
};
/**
* Create a serialization by JSON.parse/stringify
*
* @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
*
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
*
* Options:
* - `"null"`(**default**): convert it to null.
* - `"keep"`: try to keep it by additional property "undef".
* - `false`: Don't keep it, let it break.
* @remarks {@link Serialization}
* @public
*/ const JSONSerialization = (replacerAndReceiver = [
undefined$1,
undefined$1
], space, undefinedKeepingBehavior = 'null')=>({
serialization (from) {
if (undefinedKeepingBehavior && isObject(from) && 'result' in from && from.result === undefined$1) {
const alt = {
...from
};
alt.result = null;
if (undefinedKeepingBehavior === 'keep') alt.undef = true;
from = alt;
}
return JSON.stringify(from, replacerAndReceiver[0], space);
},
deserialization (serialized) {
const result = JSON.parse(serialized, replacerAndReceiver[1]);
if (isObject(result) && 'result' in result && result.result === null && 'undef' in result && result.undef === true) {
result.result = undefined$1;
delete result.undef;
}
return result;
}
});
/**
* Create a encoder by JSON.parse/stringify
*
* @public
* @param options - Options for this encoder.
* @remarks {@link IsomorphicEncoder}
*/ function JSONEncoder({ keepUndefined = 'null', replacer, reviver, space } = {}) {
return {
encode (data) {
if (keepUndefined) {
isArray(data) ? data.forEach(undefinedEncode) : undefinedEncode(data);
}
return JSON.stringify(data, replacer, space);
},
decode (encoded) {
const data = JSON.parse(encoded, reviver);
return data;
}
};
}
const undefinedEncode = (i)=>{
if ('result' in i && i.result === undefined$1) {
i.result = null;
}
};
(function(JSONEncoder) {
JSONEncoder.Default = JSONEncoder();
})(JSONEncoder || (JSONEncoder = {}));
const i = 'AsyncCall/';
// ! side effect
const AsyncCallIgnoreResponse = Symbol.for(i + 'ignored');
const AsyncCallNotify = Symbol.for(i + 'notify');
const AsyncCallBatch = Symbol.for(i + 'batch');
/**
* Wrap the AsyncCall instance to send notification.
* @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
* @example
* const notifyOnly = notify(AsyncCall(...))
* @public
*/ function notify(instanceOrFnOnInstance) {
if (isFunction(instanceOrFnOnInstance)) return instanceOrFnOnInstance[AsyncCallNotify];
return new Proxy(instanceOrFnOnInstance, {
get: notifyTrap
});
}
const notifyTrap = (target, p)=>{
return target[p][AsyncCallNotify];
};
/**
* Wrap the AsyncCall instance to use batch call.
* @param asyncCallInstance - The AsyncCall instance
* @example
* const [batched, send, drop] = batch(AsyncCall(...))
* batched.call1() // pending
* batched.call2() // pending
* send() // send all pending requests
* drop() // drop all pending requests
* @returns It will return a tuple.
*
* The first item is the batched version of AsyncCall instance passed in.
*
* The second item is a function to send all pending requests.
*
* The third item is a function to drop and reject all pending requests.
* @public
*/ // TODO: use private field in the future.
function batch(asyncCallInstance) {
const queue = [];
const getTrap = new Proxy({}, {
get (_, p) {
// @ts-expect-error
const f = (...args)=>asyncCallInstance[AsyncCallBatch](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify] = (...args)=>// @ts-expect-error
asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args);
f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
isString(p) && Object.defineProperty(methodContainer, p, {
value: f,
configurable: true
});
return f;
}
});
const methodContainer = {
__proto__: getTrap
};
return [
new Proxy(methodContainer, {
getPrototypeOf: ()=>null,
setPrototypeOf: (_, value)=>value === null
}),
()=>{
queue.length && queue.r[0]();
queue.length = 0;
},
(error = new Error('Aborted'))=>{
queue.length && queue.r[1](error);
queue.length = 0;
}
];
}
function _define_property(obj, key, value) {

@@ -23,2 +191,3 @@ if (key in obj) {

class CustomError extends Error {
// TODO: support cause
constructor(name, message, code, stack){

@@ -67,4 +236,4 @@ super(message);

try {
const E = globalDOMException();
if (type.startsWith(DOMExceptionHeader) && E) {
let E;
if (type.startsWith(DOMExceptionHeader) && (E = globalDOMException())) {
const name = type.slice(DOMExceptionHeader.length);

@@ -93,15 +262,10 @@ return new E(message, name);

};
function onAbort(signal, callback) {
signal && signal.addEventListener('abort', callback, {
once: true
});
}
const isString = (x)=>typeof x === 'string';
const isBoolean = (x)=>typeof x === 'boolean';
const isFunction = (x)=>typeof x === 'function';
const isObject = (params)=>typeof params === 'object' && params !== null;
const ERROR = 'Error';
const undefined$1 = void 0;
const Promise_resolve = (x)=>Promise.resolve(x);
const isArray = Array.isArray;
const replayFunction = ()=>'() => replay()';
const jsonrpc = '2.0';
const Request = (id, method, params, remoteStack)=>{
const makeRequest = (id, method, params, remoteStack)=>{
const x = {

@@ -118,3 +282,3 @@ jsonrpc,

};
const SuccessResponse = (id, result)=>{
const makeSuccessResponse = (id, result)=>{
const x = {

@@ -128,3 +292,3 @@ jsonrpc,

};
const ErrorResponse = (id, code, message, data)=>{
const makeErrorResponse = (id, code, message, data)=>{
if (id === undefined$1) id = null;

@@ -157,8 +321,8 @@ code = Math.floor(code);

// InternalError -32603 'Internal error'
const ErrorResponseInvalidRequest = (id)=>ErrorResponse(id, -32600, 'Invalid Request');
const ErrorResponseMethodNotFound = (id)=>ErrorResponse(id, -32601, 'Method not found');
const ErrorResponseInvalidRequest = (id)=>makeErrorResponse(id, -32600, 'Invalid Request');
const ErrorResponseMethodNotFound = (id)=>makeErrorResponse(id, -32601, 'Method not found');
const ErrorResponseMapped = (request, e, mapper)=>{
const { id } = request;
const { code , message , data } = mapper(e, request);
return ErrorResponse(id, code, message, data);
const { id } = request;
const { code, message, data } = mapper(e, request);
return makeErrorResponse(id, code, message, data);
};

@@ -170,3 +334,4 @@ const defaultErrorMapper = (stack = '', code = -1)=>(e)=>{

if (E && e instanceof E) type = DOMExceptionHeader + e.name;
if (isString(e) || typeof e === 'number' || isBoolean(e) || typeof e === 'bigint') {
const eType = typeof e;
if (eType == 'string' || eType === 'number' || eType == 'boolean' || eType == 'bigint') {
type = ERROR;

@@ -213,133 +378,2 @@ message = String(e);

//#region Serialization
/**
* Serialization implementation that do nothing
* @remarks {@link Serialization}
* @public
* @deprecated Will be removed in next major version
*/ const NoSerialization = {
serialization (from) {
return from;
},
deserialization (serialized) {
return serialized;
}
};
/**
* Create a serialization by JSON.parse/stringify
*
* @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
*
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
*
* Options:
* - `"null"`(**default**): convert it to null.
* - `"keep"`: try to keep it by additional property "undef".
* - `false`: Don't keep it, let it break.
* @remarks {@link Serialization}
* @public
*/ const JSONSerialization = (replacerAndReceiver = [
undefined$1,
undefined$1
], space, undefinedKeepingBehavior = 'null')=>({
serialization (from) {
if (undefinedKeepingBehavior && isObject(from) && 'result' in from && from.result === undefined$1) {
const alt = {
...from
};
alt.result = null;
if (undefinedKeepingBehavior === 'keep') alt.undef = true;
from = alt;
}
return JSON.stringify(from, replacerAndReceiver[0], space);
},
deserialization (serialized) {
const result = JSON.parse(serialized, replacerAndReceiver[1]);
if (isObject(result) && 'result' in result && result.result === null && 'undef' in result && result.undef === true) {
result.result = undefined$1;
delete result.undef;
}
return result;
}
} //#endregion
);
const i = 'AsyncCall/';
// ! side effect
const AsyncCallIgnoreResponse = Symbol.for(i + 'ignored');
const AsyncCallNotify = Symbol.for(i + 'notify');
const AsyncCallBatch = Symbol.for(i + 'batch');
/**
* Wrap the AsyncCall instance to send notification.
* @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
* @example
* const notifyOnly = notify(AsyncCall(...))
* @public
*/ function notify(instanceOrFnOnInstance) {
if (isFunction(instanceOrFnOnInstance)) return instanceOrFnOnInstance[AsyncCallNotify];
return new Proxy(instanceOrFnOnInstance, {
get: notifyTrap
});
}
const notifyTrap = (target, p)=>{
return target[p][AsyncCallNotify];
};
/**
* Wrap the AsyncCall instance to use batch call.
* @param asyncCallInstance - The AsyncCall instance
* @example
* const [batched, send, drop] = batch(AsyncCall(...))
* batched.call1() // pending
* batched.call2() // pending
* send() // send all pending requests
* drop() // drop all pending requests
* @returns It will return a tuple.
*
* The first item is the batched version of AsyncCall instance passed in.
*
* The second item is a function to send all pending requests.
*
* The third item is a function to drop and reject all pending requests.
* @public
*/ function batch(asyncCallInstance) {
const queue = [];
const getTrap = new Proxy({}, {
get (_, p) {
// @ts-expect-error
const f = (...args)=>asyncCallInstance[AsyncCallBatch](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify] = (...args)=>// @ts-expect-error
asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
isString(p) && Object.defineProperty(methodContainer, p, {
value: f,
configurable: true
});
return f;
}
});
const methodContainer = {
__proto__: getTrap
};
return [
new Proxy(methodContainer, {
getPrototypeOf: ()=>null,
setPrototypeOf: (_, value)=>value === null
}),
()=>{
queue.length && queue.r[0]();
queue.length = 0;
},
(error = new Error('Aborted'))=>{
queue.length && queue.r[1](error);
queue.length = 0;
}
];
}
const generateRandomID = ()=>Math.random().toString(36).slice(2);

@@ -358,3 +392,3 @@

if (!isBoolean(log)) {
const { beCalled , localError , remoteError , type , requestReplay , sendLocalStack } = log;
const { beCalled, localError, remoteError, type, requestReplay, sendLocalStack } = log;
return [

@@ -379,3 +413,3 @@ undefinedToTrue(beCalled),

if (!isBoolean(strict)) {
const { methodNotFound , unknownMessage } = strict;
const { methodNotFound, unknownMessage } = strict;
return [

@@ -429,4 +463,4 @@ methodNotFound,

if (isCallbackBasedChannel(channel)) {
channel.setup((data)=>rawMessageReceiver(data).then(rawMessageSender), (data)=>{
const _ = deserialization(data);
channel.setup((data, hint)=>rawMessageReceiver(data, hint).then(rawMessageSender), (data, hint)=>{
let _ = hintedDecode(data, hint);
if (isJSONRPCObject(_)) return true;

@@ -438,7 +472,31 @@ return Promise_resolve(_).then(isJSONRPCObject);

const m = channel;
m.on && m.on((_)=>rawMessageReceiver(_).then(rawMessageSender).then((x)=>x && m.send(x)));
m.on && m.on((_, hint)=>rawMessageReceiver(_, hint).then(rawMessageSender).then((x)=>x && m.send(x)));
}
return channel;
};
const { serializer , key: logKey = 'rpc' , strict =true , log =true , parameterStructures ='by-position' , preferLocalImplementation =false , idGenerator =generateRandomID , mapError , logger , channel , thenable } = options;
const { serializer, encoder, key: deprecatedName, name, strict = true, log = true, parameterStructures: deprecatedParameterStructures, parameterStructure, preferLocalImplementation = false, idGenerator = generateRandomID, mapError, logger, channel, thenable, signal, forceSignal } = options;
// Note: we're not shorten this error message because it will be removed in the next major version.
if (serializer && encoder) throw new TypeError('Please remove serializer.');
if (name && deprecatedName) throw new TypeError('Please remove key.');
if (deprecatedParameterStructures && parameterStructure) throw new TypeError('Please remove parameterStructure.');
const paramStyle = deprecatedParameterStructures || parameterStructure || 'by-position';
const logKey = name || deprecatedName || 'rpc';
const throwIfAborted = ()=>{
signal && signal.throwIfAborted();
forceSignal && forceSignal.throwIfAborted();
};
const { encode: encodeFromOption, encodeRequest: encodeRequestFromOption, encodeResponse: encodeResponseFromOption, decode, decodeRequest, decodeResponse } = encoder || {};
const encodeRequest = encoder ? (data)=>apply(encodeRequestFromOption || encodeFromOption, encoder, [
data
]) : serializer ? (data)=>serializer.serialization(data) : Object;
const encodeResponse = encoder ? (data)=>apply(encodeResponseFromOption || encodeFromOption, encoder, [
data
]) : serializer ? (data)=>serializer.serialization(data) : Object;
const hintedDecode = encoder ? (data, hint)=>hint == 'request' ? apply(decodeRequest || decode, encoder, [
data
]) : hint == 'response' ? apply(decodeResponse || decode, encoder, [
data
]) : apply(decode, encoder, [
data
]) : serializer ? (data)=>serializer.deserialization(data) : Object;
if (thisSideImplementation instanceof Promise) awaitThisSideImplementation();

@@ -451,13 +509,15 @@ else {

const [log_beCalled, log_localError, log_remoteError, log_pretty, log_requestReplay, log_sendLocalStack] = normalizeLogOptions(log);
const { log: console_log , error: console_error = console_log , debug: console_debug = console_log , groupCollapsed: console_groupCollapsed = console_log , groupEnd: console_groupEnd = console_log , warn: console_warn = console_log } = logger || console;
const { log: console_log, error: console_error = console_log, debug: console_debug = console_log, groupCollapsed: console_groupCollapsed = console_log, groupEnd: console_groupEnd = console_log, warn: console_warn = console_log } = logger || console;
const requestContext = new Map();
onAbort(forceSignal, ()=>{
requestContext.forEach((x)=>x[1](forceSignal.reason));
requestContext.clear();
});
const onRequest = async (data)=>{
if (signal && signal.aborted || forceSignal && forceSignal.aborted) return makeErrorObject(signal && signal.reason || forceSignal && forceSignal.reason, '', data);
if (isThisSideImplementationPending) await awaitThisSideImplementation();
else {
// not pending
if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data);
}
else if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data);
let frameworkStack = '';
try {
const { params , method , id: req_id , remoteStack } = data;
const { params, method, id: req_id, remoteStack } = data;
// ? We're mapping any method starts with 'rpc.' to a Symbol.for

@@ -476,3 +536,3 @@ const key = method.startsWith('rpc.') ? Symbol.for(method) : method;

frameworkStack = removeStackHeader(new Error().stack);
const promise = new Promise((resolve)=>resolve(executor.apply(resolvedThisSideImplementationValue, args)));
const promise = new Promise((resolve)=>resolve(apply(executor, resolvedThisSideImplementationValue, args)));
if (log_beCalled) {

@@ -482,3 +542,3 @@ if (log_pretty) {

`${logKey}.%c${method}%c(${args.map(()=>'%o').join(', ')}%c)\n%o %c@${req_id}`,
'color: #d2c057',
'color:#d2c057',
'',

@@ -488,13 +548,12 @@ ...args,

promise,
'color: gray; font-style: italic;'
'color:gray;font-style:italic;'
];
if (log_requestReplay) {
// This function will be logged to the console so it must be 1 line
// prettier-ignore
const replay = ()=>{
debugger;
return executor.apply(resolvedThisSideImplementationValue, args);
return apply(executor, resolvedThisSideImplementationValue, args);
};
replay.toString = replayFunction;
logArgs.push(replay);
// this function will be logged, keep it short.
// Do not inline it, it's hard to keep it in a single line after build step.
logArgs.push(()=>replay());
}

@@ -512,3 +571,3 @@ if (remoteStack) {

if (result === AsyncCallIgnoreResponse) return;
return SuccessResponse(req_id, result);
return makeSuccessResponse(req_id, result);
} catch (e) {

@@ -531,3 +590,3 @@ return makeErrorObject(e, frameworkStack, data);

}
const { id } = data;
const { id } = data;
if (id === null || id === undefined$1 || !requestContext.has(id)) return;

@@ -537,3 +596,4 @@ const [resolve, reject, localErrorStack = ''] = requestContext.get(id);

if ('error' in data) {
reject(RecoverError(errorType, errorMessage, errorCode, // ? We use \u0430 which looks like "a" to prevent browser think "at AsyncCall" is a real stack
reject(// TODO: add a hook to customize this
RecoverError(errorType, errorMessage, errorCode, // ? We use \u0430 which looks like "a" to prevent browser think "at AsyncCall" is a real stack
remoteErrorStack + '\n \u0430t AsyncCall (rpc) \n' + localErrorStack));

@@ -545,7 +605,7 @@ } else {

};
const rawMessageReceiver = async (_)=>{
const rawMessageReceiver = async (_, hint)=>{
let data;
let result = undefined$1;
try {
data = await deserialization(_);
data = await hintedDecode(_, hint);
if (isJSONRPCObject(data)) {

@@ -567,4 +627,7 @@ return result = await handleSingleMessage(data);

if (log_localError) console_error(e, data, result);
// todo: should check before access e.stack
return ErrorResponseParseError(e, mapError || defaultErrorMapper(e && e.stack));
let stack;
try {
stack = '' + e.stack;
} catch (e) {}
return ErrorResponseParseError(e, mapError || defaultErrorMapper(stack));
}

@@ -577,9 +640,7 @@ };

if (reply.length === 0) return;
return serialization(reply);
return encodeResponse(reply);
} else {
return serialization(res);
return encodeResponse(res);
}
};
const serialization = serializer ? (x)=>serializer.serialization(x) : Object;
const deserialization = serializer ? (x)=>serializer.deserialization(x) : Object;
if (channel instanceof Promise) channelPromise = channel.then(onChannelResolved);

@@ -592,7 +653,7 @@ else onChannelResolved(channel);

};
const sendPayload = async (payload, removeQueueR = false)=>{
const sendPayload = async (payload, removeQueueR)=>{
if (removeQueueR) payload = [
...payload
];
const data = await serialization(payload);
const data = await encodeRequest(payload);
return (resolvedChannel || await channelPromise).send(data);

@@ -610,9 +671,12 @@ };

if ('method' in data) {
const r = onRequest(data);
if ('id' in data) return r;
try {
await r;
} catch (e) {}
return undefined$1 // Does not care about return result for notifications
;
if ('id' in data) {
if (!forceSignal) return onRequest(data);
return new Promise((resolve, reject)=>{
const handleForceAbort = ()=>resolve(makeErrorObject(forceSignal.reason, '', data));
onRequest(data).then(resolve, reject).finally(()=>forceSignal.removeEventListener('abort', handleForceAbort));
onAbort(forceSignal, handleForceAbort);
});
}
onRequest(data).catch(()=>{});
return; // Skip response for notifications
}

@@ -623,2 +687,3 @@ return onResponse(data);

return new Promise((resolve, reject)=>{
throwIfAborted();
let queue = undefined$1;

@@ -644,4 +709,4 @@ if (method === AsyncCallBatch) {

stack = removeStackHeader(stack);
const param = parameterStructures === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args;
const request = Request(notify ? undefined$1 : id, method, param, log_sendLocalStack ? stack : undefined$1);
const param = paramStyle === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args;
const request = makeRequest(notify ? undefined$1 : id, method, param, log_sendLocalStack ? stack : undefined$1);
if (queue) {

@@ -707,2 +772,3 @@ queue.push(request);

exports.AsyncCall = AsyncCall;
exports.JSONEncoder = JSONEncoder;
exports.JSONSerialization = JSONSerialization;

@@ -709,0 +775,0 @@ exports.NoSerialization = NoSerialization;

@@ -1,6 +0,6 @@

!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).AsyncCall={})}(this,function(e){"use strict";function r(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}class t extends Error{constructor(e,t,n,i){super(t),r(this,"name",void 0),r(this,"code",void 0),r(this,"stack",void 0),this.name=e,this.code=n,this.stack=i}}let n={},i={},o=[{},{},n,i],l=(e,r)=>{let t=o.indexOf(e);return r.message+=`Error ${t}: https://github.com/Jack-Works/async-call-rpc/wiki/Errors#`+t,r},a={__proto__:null,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},s="DOMException:",c=(e,r,n,i)=>{try{let o=f();if(e.startsWith(s)&&o){let t=e.slice(s.length);return new o(r,t)}if(!(e in a))return new t(e,r,n,i);{let t=new a[e](r);return t.stack=i,t.code=n,t}}catch(t){return Error(`E${n} ${e}: ${r}
${i}`)}},u=e=>(e+"").replace(/^.+\n.+\n/,""),f=()=>{try{return DOMException}catch(e){}},p=e=>"string"==typeof e,y=e=>"boolean"==typeof e,d=e=>"function"==typeof e,h=e=>"object"==typeof e&&null!==e,g="Error",b=void 0,m=e=>Promise.resolve(e),w=Array.isArray,E=()=>"() => replay()",P=(e,r,t,n)=>{let i={jsonrpc:"2.0",id:e,method:r,params:t,remoteStack:n};return A(i,"id"),N(i,"remoteStack"),i},$=(e,r)=>{let t={jsonrpc:"2.0",id:e,result:r};return A(t,"id"),t},k=(e,r,t,n)=>{e===b&&(e=null),Number.isNaN(r=Math.floor(r))&&(r=-1);let i={jsonrpc:"2.0",id:e,error:{code:r,message:t,data:n}};return A(i.error,"data"),i},O=(e,r)=>{let t=v({},e,r),n=t.error;return n.code=-32700,n.message="Parse error",t},j=e=>k(e,-32600,"Invalid Request"),S=e=>k(e,-32601,"Method not found"),v=(e,r,t)=>{let{id:n}=e,{code:i,message:o,data:l}=t(r,e);return k(n,i,o,l)},x=(e="",r=-1)=>t=>{let n=z("",()=>t.message),i=z(g,(e=t.constructor)=>d(e)&&e.name),o=f();o&&t instanceof o&&(i=s+t.name),(p(t)||"number"==typeof t||y(t)||"bigint"==typeof t)&&(i=g,n=t+"");let l=e?{stack:e,type:i}:{type:i};return{code:r,message:n,data:l}},_=e=>{if(!h(e)||!("jsonrpc"in e)||"2.0"!==e.jsonrpc)return!1;if("params"in e){let r=e.params;if(!w(r)&&!h(r))return!1}return!0},z=(e,r)=>{try{let t=r();if(t===b)return e;return t+""}catch(r){return e}},A=(e,r)=>{e[r]===b&&delete e[r]},N=(e,r)=>{e[r]||delete e[r]},C="AsyncCall/",M=Symbol.for(C+"ignored"),T=Symbol.for(C+"notify"),R=Symbol.for(C+"batch"),W=(e,r)=>e[r][T],D=()=>Math.random().toString(36).slice(2),J=e=>void 0===e||e,I=e=>{if("all"===e)return[!0,!0,!0,!0,!0,!0];if(!y(e)){let{beCalled:r,localError:t,remoteError:n,type:i,requestReplay:o,sendLocalStack:l}=e;return[J(r),J(t),J(n),"basic"!==i,o,l]}return e?[!0,!0,!0,!0]:[]},q=e=>{if(!y(e)){let{methodNotFound:r,unknownMessage:t}=e;return[r,t]}return[e,e]},F=e=>"send"in e&&d(e.send),U=e=>"setup"in e&&d(e.setup);e.AsyncCall=function(e,r){let t,o,a,s,f=!0,y=async()=>{try{t=await e}catch(e){o=e,ei("AsyncCall failed to start",e)}finally{f=!1}},k=e=>(a=e,U(e)&&e.setup(e=>ep(e).then(ey),e=>{let r=eh(e);return!!_(r)||m(r).then(_)}),F(e)&&e.on&&e.on(r=>ep(r).then(ey).then(r=>r&&e.send(r))),e),{serializer:z,key:A="rpc",strict:N=!0,log:C=!0,parameterStructures:W="by-position",preferLocalImplementation:J=!1,idGenerator:B=D,mapError:G,logger:H,channel:K,thenable:L}=r;e instanceof Promise?y():(t=e,f=!1);let[Q,V]=q(N),[X,Y,Z,ee,er,et]=I(C),{log:en,error:ei=en,debug:eo=en,groupCollapsed:el=en,groupEnd:ea=en,warn:es=en}=H||console,ec=new Map,eu=async e=>{if(f)await y();else if(o)return eg(o,"",e);let r="";try{let{params:n,method:i,id:o,remoteStack:l}=e,a=i.startsWith("rpc.")?Symbol.for(i):i,s=t&&t[a];if(!d(s)){if(Q)return S(o);Y&&eo("Missing method",a,e);return}let c=w(n)?n:[n];r=u(Error().stack);let f=new Promise(e=>e(s.apply(t,c)));if(X){if(ee){let e=[`${A}.%c${i}%c(${c.map(()=>"%o").join(", ")}%c)
%o %c@${o}`,"color: #d2c057","",...c,"",f,"color: gray; font-style: italic;"];if(er){let r=()=>{debugger;return s.apply(t,c)};r.toString=E,e.push(r)}l?(el(...e),en(l),ea()):en(...e)}else en(`${A}.${i}(${[...c].toString()}) @${o}`)}let p=await f;if(p===M)return;return $(o,p)}catch(t){return eg(t,r,e)}},ef=async e=>{let r="",t="",n=0,i=g;if("error"in e){let o=e.error;r=o.message,n=o.code;let l=o.data;t=h(l)&&"stack"in l&&p(l.stack)?l.stack:"<remote stack not available>",i=h(l)&&"type"in l&&p(l.type)?l.type:g,Z&&(ee?ei(`${i}: ${r}(${n}) %c@${e.id}
%c${t}`,"color: gray",""):ei(`${i}: ${r}(${n}) @${e.id}
${t}`))}let{id:o}=e;if(null===o||o===b||!ec.has(o))return;let[l,a,s=""]=ec.get(o);ec.delete(o),"error"in e?a(c(i,r,n,t+"\n аt AsyncCall (rpc) \n"+s)):l(e.result)},ep=async e=>{let r;let t=b;try{if(r=await eh(e),_(r))return t=await ew(r);if(w(r)&&r.every(_)&&0!==r.length)return Promise.all(r.map(ew));if(!V)return b;{let e=r.id;return e===b&&(e=null),j(e)}}catch(e){return Y&&ei(e,r,t),O(e,G||x(e&&e.stack))}},ey=async e=>{if(e){if(!w(e))return ed(e);{let r=e.filter(e=>e&&"id"in e);if(0===r.length)return;return ed(r)}}},ed=z?e=>z.serialization(e):Object,eh=z?e=>z.deserialization(e):Object;K instanceof Promise?s=K.then(k):k(K);let eg=(e,r,t)=>(h(e)&&"stack"in e&&(e.stack=r.split("\n").reduce((e,r)=>e.replace(r+"\n",""),""+e.stack)),Y&&ei(e),v(t,e,G||x(et?e.stack:b))),eb=async(e,r=!1)=>{r&&(e=[...e]);let t=await ed(e);return(a||await s).send(t)},em=(e,r)=>{for(let t of e)if("id"in t){let e=ec.get(t.id);e&&e[1](r)}},ew=async e=>{if("method"in e){let r=eu(e);if("id"in e)return r;try{await r}catch(e){}return b}return ef(e)},eE=(e,r,i,o=!1)=>new Promise((a,s)=>{let c=b;if(e===R&&(c=r.shift(),e=r.shift()),"symbol"==typeof e){let r=Symbol.keyFor(e)||e.description;if(r){if(r.startsWith("rpc."))e=r;else throw TypeError("Not start with rpc.")}}else if(e.startsWith("rpc."))throw l(n,TypeError());if(J&&!f&&p(e)){let n=t&&t[e];if(d(n))return a(n(...r))}let y=B();i=u(i);let g="by-name"===W&&1===r.length&&h(r[0])?r[0]:r,m=P(o?b:y,e,g,et?i:b);if(c?(c.push(m),c.r||(c.r=[()=>eb(c,!0),e=>em(c,e)])):eb(m).catch(s),o)return a();ec.set(y,[a,s,i])}),eP=(e,r)=>{let t={[r]:(...e)=>eE(r,e,Error().stack)}[r],n={[r]:(...e)=>eE(r,e,Error().stack,!0)}[r];return t[T]=n[T]=n,p(r)&&Object.defineProperty(e$,r,{value:t,configurable:!0}),t},e$={__proto__:new Proxy({},{get:eP})};return!1===L?e$.then=b:L===b&&Object.defineProperty(e$,"then",{configurable:!0,get(){es(l(i,TypeError("RPC used as Promise: ")))}}),new Proxy(e$,{getPrototypeOf:()=>null,setPrototypeOf:(e,r)=>null===r,getOwnPropertyDescriptor:(e,r)=>(r in e$||eP(e,r),Object.getOwnPropertyDescriptor(e$,r))})},e.JSONSerialization=(e=[b,b],r,t="null")=>({serialization(n){if(t&&h(n)&&"result"in n&&n.result===b){let e={...n};e.result=null,"keep"===t&&(e.undef=!0),n=e}return JSON.stringify(n,e[0],r)},deserialization(r){let t=JSON.parse(r,e[1]);return h(t)&&"result"in t&&null===t.result&&"undef"in t&&!0===t.undef&&(t.result=b,delete t.undef),t}}),e.NoSerialization={serialization:e=>e,deserialization:e=>e},e.batch=function(e){let r=[],t=new Proxy({},{get(t,i){let o=(...t)=>e[R](r,i,...t);return o[T]=(...t)=>e[R][T](r,i,...t),o[T][T]=o[T],p(i)&&Object.defineProperty(n,i,{value:o,configurable:!0}),o}}),n={__proto__:t};return[new Proxy(n,{getPrototypeOf:()=>null,setPrototypeOf:(e,r)=>null===r}),()=>{r.length&&r.r[0](),r.length=0},(e=Error("Aborted"))=>{r.length&&r.r[1](e),r.length=0}]},e.notify=function(e){return d(e)?e[T]:new Proxy(e,{get:W})}});
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).AsyncCall={})}(this,function(e){"use strict";var r;let t=e=>"string"==typeof e,n=e=>"boolean"==typeof e,o=e=>"function"==typeof e,i=e=>"object"==typeof e&&null!==e,l="Error",a=void 0,s=e=>Promise.resolve(e),{isArray:c}=Array,{apply:u}=Reflect;function f({keepUndefined:e="null",replacer:r,reviver:t,space:n}={}){return{encode:t=>(e&&(c(t)?t.forEach(d):d(t)),JSON.stringify(t,r,n)),decode:e=>JSON.parse(e,t)}}let d=e=>{"result"in e&&e.result===a&&(e.result=null)};(r=f||(f={})).Default=r();let p="AsyncCall/",y=Symbol.for(p+"ignored"),h=Symbol.for(p+"notify"),g=Symbol.for(p+"batch"),b=(e,r)=>e[r][h];function m(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}class w extends Error{constructor(e,r,t,n){super(r),m(this,"name",void 0),m(this,"code",void 0),m(this,"stack",void 0),this.name=e,this.code=t,this.stack=n}}let E={},P={},k=[{},{},E,P],O=(e,r)=>{let t=k.indexOf(e);return r.message+=`Error ${t}: https://github.com/Jack-Works/async-call-rpc/wiki/Errors#`+t,r},$={__proto__:null,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},v="DOMException:",S=(e,r,t,n)=>{try{let o;if(e.startsWith(v)&&(o=x())){let t=e.slice(v.length);return new o(r,t)}if(!(e in $))return new w(e,r,t,n);{let o=new $[e](r);return o.stack=n,o.code=t,o}}catch(o){return Error(`E${t} ${e}: ${r}
${n}`)}},j=e=>(e+"").replace(/^.+\n.+\n/,""),x=()=>{try{return DOMException}catch(e){}};function _(e,r){e&&e.addEventListener("abort",r,{once:!0})}let N=(e,r,t,n)=>{let o={jsonrpc:"2.0",id:e,method:r,params:t,remoteStack:n};return I(o,"id"),q(o,"remoteStack"),o},z=(e,r)=>{let t={jsonrpc:"2.0",id:e,result:r};return I(t,"id"),t},A=(e,r,t,n)=>{e===a&&(e=null),Number.isNaN(r=Math.floor(r))&&(r=-1);let o={jsonrpc:"2.0",id:e,error:{code:r,message:t,data:n}};return I(o.error,"data"),o},T=(e,r)=>{let t=J({},e,r),n=t.error;return n.code=-32700,n.message="Parse error",t},R=e=>A(e,-32600,"Invalid Request"),C=e=>A(e,-32601,"Method not found"),J=(e,r,t)=>{let{id:n}=e,{code:o,message:i,data:l}=t(r,e);return A(n,o,i,l)},M=(e="",r=-1)=>t=>{let n=W("",()=>t.message),i=W(l,(e=t.constructor)=>o(e)&&e.name),a=x();a&&t instanceof a&&(i=v+t.name);let s=typeof t;return("string"==s||"number"===s||"boolean"==s||"bigint"==s)&&(i=l,n=t+""),{code:r,message:n,data:e?{stack:e,type:i}:{type:i}}},D=e=>{if(!i(e)||!("jsonrpc"in e)||"2.0"!==e.jsonrpc)return!1;if("params"in e){let r=e.params;if(!c(r)&&!i(r))return!1}return!0},W=(e,r)=>{try{let t=r();if(t===a)return e;return t+""}catch(r){return e}},I=(e,r)=>{e[r]===a&&delete e[r]},q=(e,r)=>{e[r]||delete e[r]},L=()=>Math.random().toString(36).slice(2),F=e=>void 0===e||e,U=e=>{if("all"===e)return[!0,!0,!0,!0,!0,!0];if(!n(e)){let{beCalled:r,localError:t,remoteError:n,type:o,requestReplay:i,sendLocalStack:l}=e;return[F(r),F(t),F(n),"basic"!==o,i,l]}return e?[!0,!0,!0,!0]:[]},B=e=>{if(!n(e)){let{methodNotFound:r,unknownMessage:t}=e;return[r,t]}return[e,e]},G=e=>"send"in e&&o(e.send),H=e=>"setup"in e&&o(e.setup);e.AsyncCall=function(e,r){let n,f,d,p,b=!0,m=async()=>{try{n=await e}catch(e){f=e,ek("AsyncCall failed to start",e)}finally{b=!1}},w=e=>(d=e,H(e)&&e.setup((e,r)=>eN(e,r).then(ez),(e,r)=>{let t=ed(e,r);return!!D(t)||s(t).then(D)}),G(e)&&e.on&&e.on((r,t)=>eN(r,t).then(ez).then(r=>r&&e.send(r))),e),{serializer:k,encoder:$,key:v,name:x,strict:A=!0,log:W=!0,parameterStructures:I,parameterStructure:q,preferLocalImplementation:F=!1,idGenerator:K=L,mapError:Q,logger:V,channel:X,thenable:Y,signal:Z,forceSignal:ee}=r;if(k&&$)throw TypeError("Please remove serializer.");if(x&&v)throw TypeError("Please remove key.");if(I&&q)throw TypeError("Please remove parameterStructure.");let er=I||q||"by-position",et=x||v||"rpc",en=()=>{Z&&Z.throwIfAborted(),ee&&ee.throwIfAborted()},{encode:eo,encodeRequest:ei,encodeResponse:el,decode:ea,decodeRequest:es,decodeResponse:ec}=$||{},eu=$?e=>u(ei||eo,$,[e]):k?e=>k.serialization(e):Object,ef=$?e=>u(el||eo,$,[e]):k?e=>k.serialization(e):Object,ed=$?(e,r)=>"request"==r?u(es||ea,$,[e]):"response"==r?u(ec||ea,$,[e]):u(ea,$,[e]):k?e=>k.deserialization(e):Object;e instanceof Promise?m():(n=e,b=!1);let[ep,ey]=B(A),[eh,eg,eb,em,ew,eE]=U(W),{log:eP,error:ek=eP,debug:eO=eP,groupCollapsed:e$=eP,groupEnd:ev=eP,warn:eS=eP}=V||console,ej=new Map;_(ee,()=>{ej.forEach(e=>e[1](ee.reason)),ej.clear()});let ex=async e=>{if(Z&&Z.aborted||ee&&ee.aborted)return eA(Z&&Z.reason||ee&&ee.reason,"",e);if(b)await m();else if(f)return eA(f,"",e);let r="";try{let{params:t,method:i,id:l,remoteStack:a}=e,s=i.startsWith("rpc.")?Symbol.for(i):i,f=n&&n[s];if(!o(f)){if(ep)return C(l);eg&&eO("Missing method",s,e);return}let d=c(t)?t:[t];r=j(Error().stack);let p=new Promise(e=>e(u(f,n,d)));if(eh){if(em){let e=[`${et}.%c${i}%c(${d.map(()=>"%o").join(", ")}%c)
%o %c@${l}`,"color:#d2c057","",...d,"",p,"color:gray;font-style:italic;"];if(ew){let r=()=>{debugger;return u(f,n,d)};e.push(()=>r())}a?(e$(...e),eP(a),ev()):eP(...e)}else eP(`${et}.${i}(${[...d].toString()}) @${l}`)}let h=await p;if(h===y)return;return z(l,h)}catch(t){return eA(t,r,e)}},e_=async e=>{let r="",n="",o=0,s=l;if("error"in e){let a=e.error;r=a.message,o=a.code;let c=a.data;n=i(c)&&"stack"in c&&t(c.stack)?c.stack:"<remote stack not available>",s=i(c)&&"type"in c&&t(c.type)?c.type:l,eb&&(em?ek(`${s}: ${r}(${o}) %c@${e.id}
%c${n}`,"color: gray",""):ek(`${s}: ${r}(${o}) @${e.id}
${n}`))}let{id:c}=e;if(null===c||c===a||!ej.has(c))return;let[u,f,d=""]=ej.get(c);ej.delete(c),"error"in e?f(S(s,r,o,n+"\n аt AsyncCall (rpc) \n"+d)):u(e.result)},eN=async(e,r)=>{let t;let n=a;try{if(t=await ed(e,r),D(t))return n=await eC(t);if(c(t)&&t.every(D)&&0!==t.length)return Promise.all(t.map(eC));if(!ey)return a;{let e=t.id;return e===a&&(e=null),R(e)}}catch(r){let e;eg&&ek(r,t,n);try{e=""+r.stack}catch(e){}return T(r,Q||M(e))}},ez=async e=>{if(e){if(!c(e))return ef(e);{let r=e.filter(e=>e&&"id"in e);if(0===r.length)return;return ef(r)}}};X instanceof Promise?p=X.then(w):w(X);let eA=(e,r,t)=>(i(e)&&"stack"in e&&(e.stack=r.split("\n").reduce((e,r)=>e.replace(r+"\n",""),""+e.stack)),eg&&ek(e),J(t,e,Q||M(eE?e.stack:a))),eT=async(e,r)=>{r&&(e=[...e]);let t=await eu(e);return(d||await p).send(t)},eR=(e,r)=>{for(let t of e)if("id"in t){let e=ej.get(t.id);e&&e[1](r)}},eC=async e=>"method"in e?"id"in e?ee?new Promise((r,t)=>{let n=()=>r(eA(ee.reason,"",e));ex(e).then(r,t).finally(()=>ee.removeEventListener("abort",n)),_(ee,n)}):ex(e):void ex(e).catch(()=>{}):e_(e),eJ=(e,r,l,s=!1)=>new Promise((c,u)=>{en();let f=a;if(e===g&&(f=r.shift(),e=r.shift()),"symbol"==typeof e){let r=Symbol.keyFor(e)||e.description;if(r){if(r.startsWith("rpc."))e=r;else throw TypeError("Not start with rpc.")}}else if(e.startsWith("rpc."))throw O(E,TypeError());if(F&&!b&&t(e)){let t=n&&n[e];if(o(t))return c(t(...r))}let d=K();l=j(l);let p="by-name"===er&&1===r.length&&i(r[0])?r[0]:r,y=N(s?a:d,e,p,eE?l:a);if(f?(f.push(y),f.r||(f.r=[()=>eT(f,!0),e=>eR(f,e)])):eT(y).catch(u),s)return c();ej.set(d,[c,u,l])}),eM=(e,r)=>{let n={[r]:(...e)=>eJ(r,e,Error().stack)}[r],o={[r]:(...e)=>eJ(r,e,Error().stack,!0)}[r];return n[h]=o[h]=o,t(r)&&Object.defineProperty(eD,r,{value:n,configurable:!0}),n},eD={__proto__:new Proxy({},{get:eM})};return!1===Y?eD.then=a:Y===a&&Object.defineProperty(eD,"then",{configurable:!0,get(){eS(O(P,TypeError("RPC used as Promise: ")))}}),new Proxy(eD,{getPrototypeOf:()=>null,setPrototypeOf:(e,r)=>null===r,getOwnPropertyDescriptor:(e,r)=>(r in eD||eM(e,r),Object.getOwnPropertyDescriptor(eD,r))})},e.JSONEncoder=f,e.JSONSerialization=(e=[a,a],r,t="null")=>({serialization(n){if(t&&i(n)&&"result"in n&&n.result===a){let e={...n};e.result=null,"keep"===t&&(e.undef=!0),n=e}return JSON.stringify(n,e[0],r)},deserialization(r){let t=JSON.parse(r,e[1]);return i(t)&&"result"in t&&null===t.result&&"undef"in t&&!0===t.undef&&(t.result=a,delete t.undef),t}}),e.NoSerialization={serialization:e=>e,deserialization:e=>e},e.batch=function(e){let r=[],n={__proto__:new Proxy({},{get(o,i){let l=(...t)=>e[g](r,i,...t);return l[h]=(...t)=>e[g][h](r,i,...t),l[h][h]=l[h],t(i)&&Object.defineProperty(n,i,{value:l,configurable:!0}),l}})};return[new Proxy(n,{getPrototypeOf:()=>null,setPrototypeOf:(e,r)=>null===r}),()=>{r.length&&r.r[0](),r.length=0},(e=Error("Aborted"))=>{r.length&&r.r[1](e),r.length=0}]},e.notify=function(e){return o(e)?e[h]:new Proxy(e,{get:b})}});
//# sourceMappingURL=base.min.js.map

@@ -9,2 +9,19 @@ /**

/**
* AbortSignal
* @public
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* This is a subset of the AbortSignal interface defined in the [WinterCG](https://wintercg.org/).
*/
export declare interface AbortSignalLike {
readonly aborted: boolean;
addEventListener(type: 'abort', listener: () => void, options: {
once: boolean;
}): void;
removeEventListener(type: 'abort', listener: () => void): void;
throwIfAborted(): void;
reason: any;
}
/**
* Create a RPC server & client.

@@ -30,15 +47,20 @@ *

/**
* Log options of AsyncCall
* Log options
* @remarks
* This option controls how AsyncCall should log RPC calls to the console.
* This option controls how AsyncCall log requests to the console.
* @public
* @privateRemarks
* TODO: rename to AsyncCallLogOptions
* TODO: split to server log and client log
*/
export declare interface AsyncCallLogLevel {
/**
* Log all requests to this instance
* Log all incoming requests
* @defaultValue true
* @privateRemarks
* TODO: rename to called
*/
beCalled?: boolean;
/**
* Log all errors produced when responding requests
* Log all errors when responding requests
* @defaultValue true

@@ -48,3 +70,3 @@ */

/**
* Log remote errors
* Log errors from the remote
* @defaultValue true

@@ -54,10 +76,12 @@ */

/**
* Send the call stack to the remote when making requests
* Send the stack to the remote when making requests
* @defaultValue false
* @privateRemarks
* TODO: rename this field to sendRequestStack and move it to AsyncCallOptions.
*/
sendLocalStack?: boolean;
/**
* Control if AsyncCall should make the log better
* Style of the log
* @remarks
* If use "pretty", it will call the logger with some CSS to make the log easier to read.
* If this option is set to "pretty", it will log with some CSS to make the log easier to read in the browser devtools.
* Check out this article to know more about it: {@link https://dev.to/annlin/consolelog-with-css-style-1mmp | Console.log with CSS Style}

@@ -68,5 +92,5 @@ * @defaultValue 'pretty'

/**
* Log a function that allows to execute the request with same arguments again
* If log a function that can replay the request
* @remarks
* Do not use this options in the production environment because it will log a closure that captures the arguments of requests. This may cause memory leak.
* Do not use this options in the production environment because it will log a closure that captures all arguments of requests. This may cause memory leak.
* @defaultValue false

@@ -81,29 +105,36 @@ */

*/
export declare interface AsyncCallOptions {
export declare interface AsyncCallOptions<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* This option is used for better log print.
* Name used when pretty log is enabled.
* @defaultValue `rpc`
* @deprecated Renamed to "name".
*/
key?: string;
/**
* How to serialize and deserialize the JSON RPC payload
*
* Name used when pretty log is enabled.
* @defaultValue `rpc`
*/
name?: string;
/**
* Serializer of the requests and responses.
* @deprecated Use "encoding" option instead. This option will be removed in the next major version.
* @see {@link Serialization}.
*/
serializer?: Serialization;
/**
* Encoder of requests and responses.
* @see {@link IsomorphicEncoder} or {@link IsomorphicEncoderFull}.
* @remarks
* See {@link Serialization}.
* There is some built-in serializer:
* There are some built-in encoders:
*
* - {@link NoSerialization} (Not doing anything to the message)
*
* - {@link JSONSerialization} (Using JSON.parse/stringify in the backend)
*
* - {@link https://github.com/jack-works/async-call-rpc#web-deno-and-node-bson | BSONSerialization} (use the {@link https://npmjs.org/bson | bson} as the serializer)
*
* @defaultValue {@link NoSerialization}
* - JSONEncoder: is using JSON.parser and JSON.stringify under the hood.
* @defaultValue undefined
*/
serializer?: Serialization;
encoder?: IsomorphicEncoder<EncodedRequest, EncodedResponse> | IsomorphicEncoderFull<EncodedRequest, EncodedResponse>;
/**
* Provide the logger of AsyncCall
* @remarks
* See {@link ConsoleInterface}
* Provide the logger
* @see {@link ConsoleInterface}
* @defaultValue globalThis.console
* @privateRemarks
* TODO: allow post-create tweak?
*/

@@ -115,8 +146,10 @@ logger?: ConsoleInterface;

* {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/websocket.client.ts | Example for CallbackBasedChannel} or {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/websocket.server.ts | Example for EventBasedChannel}
* @privateRemarks
* TODO: split to ClientChannel (onResponse, send) and IsomorphicChannel
*/
channel: CallbackBasedChannel | EventBasedChannel | Promise<CallbackBasedChannel | EventBasedChannel>;
channel: CallbackBasedChannel<EncodedRequest | EncodedResponse> | EventBasedChannel<EncodedRequest | EncodedResponse> | Promise<CallbackBasedChannel<EncodedRequest | EncodedResponse> | EventBasedChannel<EncodedRequest | EncodedResponse>>;
/**
* Choose log level.
* @remarks
* - `true` is a reasonable default value, which means all options are the default options in the {@link AsyncCallLogLevel}
* - `true` is a reasonable default value, which means all options are the default in the {@link AsyncCallLogLevel}
*

@@ -127,6 +160,9 @@ * - `false` is disable all logs

* @defaultValue true
* @privateRemarks
* TODO: allow post-create tweak?
*/
log?: AsyncCallLogLevel | boolean | 'all';
/**
* Control the behavior that different from the JSON RPC spec. See {@link AsyncCallStrictJSONRPC}
* Control the behavior that different from the JSON-RPC spec
* @see {@link AsyncCallStrictJSONRPC}
* @remarks

@@ -140,13 +176,22 @@ * - `true` is to enable all strict options

* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* See {@link https://www.jsonrpc.org/specification#parameter_structures}
* @deprecated renamed to "parameterStructure"
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name';
/**
* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* When using `by-name`, only first parameter of the requests are sent to the remote and it must be an object.
*
* @privateRemarks
* TODO: review the edge cases when using "by-name".
* TODO: throw an error/print a warning when using "by-name" and the first parameter is not an object/more than 1 parameter is given.
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name';
parameterStructure?: 'by-position' | 'by-name';
/**

@@ -165,16 +210,18 @@ * Prefer local implementation than remote.

/**
* Control how to report error response according to the exception
* Change the {@link ErrorResponseDetail}.
* @privateRemarks
* TODO: provide a JSONRPCError class to allow customizing ErrorResponseDetail without mapError.
*/
mapError?: ErrorMapFunction<unknown>;
/**
* If the instance should be "thenable".
* If the instance is "thenable".
* @defaultValue undefined
* @remarks
* If this options is set to `true`, it will return a `then` method normally (forwards the call to the remote).
* If this options is set to `true`, it will return a `then` method normally (forwards the request to the remote).
*
* If this options is set to `false`, it will return `undefined` even the remote has a method called "then".
* If this options is set to `false`, it will return `undefined`, which means a method named "then" on the remote is not reachable.
*
* If this options is set to `undefined`, it will return `undefined` and show a warning. You must explicitly set this option to `true` or `false` to dismiss the warning.
*
* The motivation of this option is to resolve the problem caused by Promise auto-unwrapping.
* This option is used to resolve the problem caused by Promise auto-unwrapping.
*

@@ -194,13 +241,39 @@ * Consider this code:

thenable?: boolean;
/**
* AbortSignal to stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, except for the pending ones.
*/
signal?: AbortSignalLike;
/**
* AbortSignal to force stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, and the pending requests will be forcibly rejected and pending results will be ignored.
*/
forceSignal?: AbortSignalLike;
}
/**
* Control the behavior that different from the JSON RPC spec.
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
* @deprecated renamed to {@link AsyncCallStrictOptions}
*/
export declare interface AsyncCallStrictJSONRPC {
export declare interface AsyncCallStrictJSONRPC extends AsyncCallStrictOptions {
}
/**
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
*/
export declare interface AsyncCallStrictOptions {
/**
* Controls if AsyncCall send an ErrorResponse when the requested method is not defined.
* @remarks
* Set this options to false, AsyncCall will ignore the request (but print a log) if the method is not defined.
* If this option is set to false, AsyncCall will ignore the request and print a log if the method is not defined.
* @defaultValue true

@@ -212,3 +285,4 @@ */

* @remarks
* Set this options to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload. This is useful when the message channel is also used to transfer other kinds of messages.
* If this option is set to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload.
* This is useful when the message channel is also used to transfer other kinds of messages.
* @defaultValue true

@@ -274,3 +348,3 @@ */

/**
* Make all function in the type T becomes async functions and filtering non-Functions out.
* Make all functions in T becomes an async function and filter non-Functions out.
*

@@ -324,6 +398,23 @@ * @remarks

*/
setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown) => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown) => boolean | Promise<boolean>): (() => void) | void;
setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown, hint?: undefined | 'request' | 'response') => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown, hint?: undefined | 'request' | 'response') => boolean | Promise<boolean>): (() => void) | void;
}
/**
* Encoder of the client.
* @public
*/
export declare interface ClientEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request object.
* @param data - The request object
*/
encodeRequest(data: Requests): EncodedRequest | PromiseLike<EncodedRequest>;
/**
* Decode the response object.
* @param encoded - The encoded response object
*/
decodeResponse(encoded: EncodedResponse): Responses | PromiseLike<Responses>;
}
/**
* The minimal Console interface that AsyncCall needs.

@@ -349,4 +440,6 @@ * @public

* @param request - The request object
* @privateRemarks
* TODO: remove T generic parameter
*/
export declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<JSONRPCRequest>) => {
export declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<Request>) => {
code: number;

@@ -358,6 +451,24 @@ message: string;

/**
* ! This file MUST NOT contain any import statement.
* ! This file is part of public API of this package (for Deno users).
* JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export declare interface ErrorResponse<Error = unknown> {
readonly jsonrpc: '2.0';
readonly id?: ID;
readonly error: ErrorResponseDetail<Error>;
}
/**
* The "error" record on the JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export declare interface ErrorResponseDetail<Error = unknown> {
readonly code: number;
readonly message: string;
readonly data?: Error;
}
/**
* This interface represents a "on message" - "send response" model.

@@ -375,3 +486,3 @@ * @remarks

*/
on(listener: (data: Data) => void): void | (() => void);
on(listener: (data: Data, hint?: 'request' | 'response' | undefined) => void): void | (() => void);
/**

@@ -381,6 +492,12 @@ * Send the data to the remote side.

*/
send(data: Data): void;
send(data: Data): void | Promise<void>;
}
/**
* ID of a JSON-RPC request/response.
* @public
*/
export declare type ID = string | number | null | undefined;
/**
* Make the returning type to `Promise<void>`

@@ -397,2 +514,26 @@ * @internal

/**
* Encoder that work for both server and client.
* @public
*/
export declare interface IsomorphicEncoder<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request or response object.
* @param data - The request or response object
*/
encode(data: Requests | Responses): EncodedRequest | EncodedResponse | PromiseLike<EncodedRequest | EncodedResponse>;
/**
* Decode the request or response object.
* @param encoded - The encoded request or response object
*/
decode(encoded: EncodedRequest | EncodedResponse): Requests | Responses | PromiseLike<Requests | Responses>;
}
/**
* Encoder that work for both server and client.
* @public
*/
export declare interface IsomorphicEncoderFull<EncodedRequest = unknown, EncodedResponse = unknown> extends ClientEncoding<EncodedRequest, EncodedResponse>, ServerEncoding<EncodedRequest, EncodedResponse>, Partial<Pick<IsomorphicEncoder, 'decode'>> {
}
/** @internal */

@@ -405,13 +546,43 @@ export declare type _IteratorLikeToAsyncGenerator<T extends _IteratorOrIterableFunction> = T extends (...args: any) => AsyncGenerator<any> ? T : T extends (...args: infer Args) => Iterator<infer Yield, infer Return, infer Next> | Iterable<infer Yield> | AsyncIterator<infer Yield, infer Return, infer Next> | AsyncIterable<infer Yield> ? (...args: Args) => AsyncGenerator<Yield, Return, Next> : never;

/**
* JSON RPC Request object
* Create a encoder by JSON.parse/stringify
*
* @public
* @param options - Options for this encoder.
* @remarks {@link IsomorphicEncoder}
*/
export declare type JSONRPCRequest = {
jsonrpc: '2.0';
id?: string | number | null;
method: string;
params: readonly unknown[] | object;
};
export declare function JSONEncoder({ keepUndefined, replacer, reviver, space, }?: JSONEncoderOptions): IsomorphicEncoder;
/** @public */
export declare namespace JSONEncoder {
const Default: IsomorphicEncoder<unknown, unknown>;
}
/**
* @public
* Options of {@link (JSONEncoder:function)}
*/
export declare interface JSONEncoderOptions {
/** Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */
space?: string | number | undefined;
/**
* How to handle `"undefined"` in the result of {@link SuccessResponse}.
*
* @remarks
* If you need a full support of encoding `undefined`, for example, when the result is `{ field: undefined }` and you want to keep it,
* you need to find another library to do this.
*
* If this is not handled properly, `JSON.stringify` will emit an invalid JSON-RPC object (fields with `undefined` value will be omitted).
*
* Options:
* - `"null"`(**default**): convert it to `null`.
* - `false`: do not do anything, let it break.
*/
keepUndefined?: false | 'null' | undefined;
/** A function that transforms the results. */
replacer?: ((this: any, key: string, value: any) => any) | undefined;
/** A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. */
reviver?: ((this: any, key: string, value: any) => any) | undefined;
}
/**
* Create a serialization by JSON.parse/stringify

@@ -452,5 +623,41 @@ *

/**
* Serialize and deserialize of the JSON RPC payload
* JSON-RPC Request object.
* @public
* @see https://www.jsonrpc.org/specification#request_object
*/
export declare interface Request {
readonly jsonrpc: '2.0';
readonly id?: ID;
readonly method: string;
readonly params: readonly unknown[] | object;
/**
* Non-standard field. It records the caller's stack of this Request.
*/
readonly remoteStack?: string | undefined;
}
/**
* A request object or an array of request objects.
* @public
*/
export declare type Requests = Request | readonly Request[];
/**
* A JSON-RPC response object
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export declare type Response = SuccessResponse | ErrorResponse;
/**
* A response object or an array of response objects.
* @public
*/
export declare type Responses = Response | readonly Response[];
/**
* Serialize and deserialize of the JSON-RPC payload
* @public
* @deprecated Use {@link IsomorphicEncoder} instead.
*/
export declare interface Serialization {

@@ -469,2 +676,45 @@ /**

/**
* Encoder of the server.
* @public
*/
export declare interface ServerEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the response object.
* @param data - The response object
*/
decodeRequest(encoded: EncodedRequest): Requests | PromiseLike<Requests>;
/**
* Decode the request object.
* @param encoded - The encoded request object
*/
encodeResponse(data: Responses): EncodedResponse | PromiseLike<EncodedResponse>;
}
/**
* JSON-RPC SuccessResponse object.
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export declare interface SuccessResponse {
readonly jsonrpc: '2.0';
readonly id?: ID;
result: unknown;
/**
* Non-standard property
* @remarks
* This is a non-standard field that used to represent the result field should be `undefined` instead of `null`.
*
* A field with value `undefined` will be omitted in `JSON.stringify`,
* and if the `"result"` field is omitted, this is no longer a valid JSON-RPC response object.
*
* By default, AsyncCall will convert `undefined` to `null` to keep the response valid, but it _won't_ add this field.
*
* Set `keepUndefined` in JSONEncoderOptions to `"keep"` will add this field.
*
* This field starts with a space, so TypeScript will hide it when providing completion.
*/
undef?: unknown;
}
export { }

@@ -8,2 +8,171 @@ /// <reference types="./full.d.ts" />

const isString = (x)=>typeof x === 'string';
const isBoolean = (x)=>typeof x === 'boolean';
const isFunction = (x)=>typeof x === 'function';
const isObject = (params)=>typeof params === 'object' && params !== null;
const ERROR = 'Error';
const undefined$1 = void 0;
const { setPrototypeOf } = Object;
const Promise_resolve = (x)=>Promise.resolve(x);
const { isArray } = Array;
const { apply } = Reflect;
/**
* Serialization implementation that do nothing
* @remarks {@link Serialization}
* @public
* @deprecated Will be removed in next major version
*/ const NoSerialization = {
serialization (from) {
return from;
},
deserialization (serialized) {
return serialized;
}
};
/**
* Create a serialization by JSON.parse/stringify
*
* @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
*
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
*
* Options:
* - `"null"`(**default**): convert it to null.
* - `"keep"`: try to keep it by additional property "undef".
* - `false`: Don't keep it, let it break.
* @remarks {@link Serialization}
* @public
*/ const JSONSerialization = (replacerAndReceiver = [
undefined$1,
undefined$1
], space, undefinedKeepingBehavior = 'null')=>({
serialization (from) {
if (undefinedKeepingBehavior && isObject(from) && 'result' in from && from.result === undefined$1) {
const alt = {
...from
};
alt.result = null;
if (undefinedKeepingBehavior === 'keep') alt.undef = true;
from = alt;
}
return JSON.stringify(from, replacerAndReceiver[0], space);
},
deserialization (serialized) {
const result = JSON.parse(serialized, replacerAndReceiver[1]);
if (isObject(result) && 'result' in result && result.result === null && 'undef' in result && result.undef === true) {
result.result = undefined$1;
delete result.undef;
}
return result;
}
});
/**
* Create a encoder by JSON.parse/stringify
*
* @public
* @param options - Options for this encoder.
* @remarks {@link IsomorphicEncoder}
*/ function JSONEncoder({ keepUndefined = 'null', replacer, reviver, space } = {}) {
return {
encode (data) {
if (keepUndefined) {
isArray(data) ? data.forEach(undefinedEncode) : undefinedEncode(data);
}
return JSON.stringify(data, replacer, space);
},
decode (encoded) {
const data = JSON.parse(encoded, reviver);
return data;
}
};
}
const undefinedEncode = (i)=>{
if ('result' in i && i.result === undefined$1) {
i.result = null;
}
};
(function(JSONEncoder) {
JSONEncoder.Default = JSONEncoder();
})(JSONEncoder || (JSONEncoder = {}));
const i$1 = 'AsyncCall/';
// ! side effect
const AsyncCallIgnoreResponse = Symbol.for(i$1 + 'ignored');
const AsyncCallNotify = Symbol.for(i$1 + 'notify');
const AsyncCallBatch = Symbol.for(i$1 + 'batch');
/**
* Wrap the AsyncCall instance to send notification.
* @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
* @example
* const notifyOnly = notify(AsyncCall(...))
* @public
*/ function notify(instanceOrFnOnInstance) {
if (isFunction(instanceOrFnOnInstance)) return instanceOrFnOnInstance[AsyncCallNotify];
return new Proxy(instanceOrFnOnInstance, {
get: notifyTrap
});
}
const notifyTrap = (target, p)=>{
return target[p][AsyncCallNotify];
};
/**
* Wrap the AsyncCall instance to use batch call.
* @param asyncCallInstance - The AsyncCall instance
* @example
* const [batched, send, drop] = batch(AsyncCall(...))
* batched.call1() // pending
* batched.call2() // pending
* send() // send all pending requests
* drop() // drop all pending requests
* @returns It will return a tuple.
*
* The first item is the batched version of AsyncCall instance passed in.
*
* The second item is a function to send all pending requests.
*
* The third item is a function to drop and reject all pending requests.
* @public
*/ // TODO: use private field in the future.
function batch(asyncCallInstance) {
const queue = [];
const getTrap = new Proxy({}, {
get (_, p) {
// @ts-expect-error
const f = (...args)=>asyncCallInstance[AsyncCallBatch](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify] = (...args)=>// @ts-expect-error
asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args);
f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
isString(p) && Object.defineProperty(methodContainer, p, {
value: f,
configurable: true
});
return f;
}
});
const methodContainer = {
__proto__: getTrap
};
return [
new Proxy(methodContainer, {
getPrototypeOf: ()=>null,
setPrototypeOf: (_, value)=>value === null
}),
()=>{
queue.length && queue.r[0]();
queue.length = 0;
},
(error = new Error('Aborted'))=>{
queue.length && queue.r[1](error);
queue.length = 0;
}
];
}
function _define_property$1(obj, key, value) {

@@ -23,2 +192,3 @@ if (key in obj) {

class CustomError extends Error {
// TODO: support cause
constructor(name, message, code, stack){

@@ -67,4 +237,4 @@ super(message);

try {
const E = globalDOMException();
if (type.startsWith(DOMExceptionHeader) && E) {
let E;
if (type.startsWith(DOMExceptionHeader) && (E = globalDOMException())) {
const name = type.slice(DOMExceptionHeader.length);

@@ -93,16 +263,10 @@ return new E(message, name);

};
function onAbort(signal, callback) {
signal && signal.addEventListener('abort', callback, {
once: true
});
}
const isString = (x)=>typeof x === 'string';
const isBoolean = (x)=>typeof x === 'boolean';
const isFunction = (x)=>typeof x === 'function';
const isObject = (params)=>typeof params === 'object' && params !== null;
const ERROR = 'Error';
const undefined$1 = void 0;
const Object_setPrototypeOf = Object.setPrototypeOf;
const Promise_resolve = (x)=>Promise.resolve(x);
const isArray = Array.isArray;
const replayFunction = ()=>'() => replay()';
const jsonrpc = '2.0';
const Request = (id, method, params, remoteStack)=>{
const makeRequest = (id, method, params, remoteStack)=>{
const x = {

@@ -119,3 +283,3 @@ jsonrpc,

};
const SuccessResponse = (id, result)=>{
const makeSuccessResponse = (id, result)=>{
const x = {

@@ -129,3 +293,3 @@ jsonrpc,

};
const ErrorResponse = (id, code, message, data)=>{
const makeErrorResponse = (id, code, message, data)=>{
if (id === undefined$1) id = null;

@@ -158,8 +322,8 @@ code = Math.floor(code);

// InternalError -32603 'Internal error'
const ErrorResponseInvalidRequest = (id)=>ErrorResponse(id, -32600, 'Invalid Request');
const ErrorResponseMethodNotFound = (id)=>ErrorResponse(id, -32601, 'Method not found');
const ErrorResponseInvalidRequest = (id)=>makeErrorResponse(id, -32600, 'Invalid Request');
const ErrorResponseMethodNotFound = (id)=>makeErrorResponse(id, -32601, 'Method not found');
const ErrorResponseMapped = (request, e, mapper)=>{
const { id } = request;
const { code , message , data } = mapper(e, request);
return ErrorResponse(id, code, message, data);
const { id } = request;
const { code, message, data } = mapper(e, request);
return makeErrorResponse(id, code, message, data);
};

@@ -171,3 +335,4 @@ const defaultErrorMapper = (stack = '', code = -1)=>(e)=>{

if (E && e instanceof E) type = DOMExceptionHeader + e.name;
if (isString(e) || typeof e === 'number' || isBoolean(e) || typeof e === 'bigint') {
const eType = typeof e;
if (eType == 'string' || eType === 'number' || eType == 'boolean' || eType == 'bigint') {
type = ERROR;

@@ -214,133 +379,2 @@ message = String(e);

//#region Serialization
/**
* Serialization implementation that do nothing
* @remarks {@link Serialization}
* @public
* @deprecated Will be removed in next major version
*/ const NoSerialization = {
serialization (from) {
return from;
},
deserialization (serialized) {
return serialized;
}
};
/**
* Create a serialization by JSON.parse/stringify
*
* @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
* @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
*
* If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
*
* Options:
* - `"null"`(**default**): convert it to null.
* - `"keep"`: try to keep it by additional property "undef".
* - `false`: Don't keep it, let it break.
* @remarks {@link Serialization}
* @public
*/ const JSONSerialization = (replacerAndReceiver = [
undefined$1,
undefined$1
], space, undefinedKeepingBehavior = 'null')=>({
serialization (from) {
if (undefinedKeepingBehavior && isObject(from) && 'result' in from && from.result === undefined$1) {
const alt = {
...from
};
alt.result = null;
if (undefinedKeepingBehavior === 'keep') alt.undef = true;
from = alt;
}
return JSON.stringify(from, replacerAndReceiver[0], space);
},
deserialization (serialized) {
const result = JSON.parse(serialized, replacerAndReceiver[1]);
if (isObject(result) && 'result' in result && result.result === null && 'undef' in result && result.undef === true) {
result.result = undefined$1;
delete result.undef;
}
return result;
}
} //#endregion
);
const i$1 = 'AsyncCall/';
// ! side effect
const AsyncCallIgnoreResponse = Symbol.for(i$1 + 'ignored');
const AsyncCallNotify = Symbol.for(i$1 + 'notify');
const AsyncCallBatch = Symbol.for(i$1 + 'batch');
/**
* Wrap the AsyncCall instance to send notification.
* @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
* @example
* const notifyOnly = notify(AsyncCall(...))
* @public
*/ function notify(instanceOrFnOnInstance) {
if (isFunction(instanceOrFnOnInstance)) return instanceOrFnOnInstance[AsyncCallNotify];
return new Proxy(instanceOrFnOnInstance, {
get: notifyTrap
});
}
const notifyTrap = (target, p)=>{
return target[p][AsyncCallNotify];
};
/**
* Wrap the AsyncCall instance to use batch call.
* @param asyncCallInstance - The AsyncCall instance
* @example
* const [batched, send, drop] = batch(AsyncCall(...))
* batched.call1() // pending
* batched.call2() // pending
* send() // send all pending requests
* drop() // drop all pending requests
* @returns It will return a tuple.
*
* The first item is the batched version of AsyncCall instance passed in.
*
* The second item is a function to send all pending requests.
*
* The third item is a function to drop and reject all pending requests.
* @public
*/ function batch(asyncCallInstance) {
const queue = [];
const getTrap = new Proxy({}, {
get (_, p) {
// @ts-expect-error
const f = (...args)=>asyncCallInstance[AsyncCallBatch](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify] = (...args)=>// @ts-expect-error
asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args);
// @ts-expect-error
f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
isString(p) && Object.defineProperty(methodContainer, p, {
value: f,
configurable: true
});
return f;
}
});
const methodContainer = {
__proto__: getTrap
};
return [
new Proxy(methodContainer, {
getPrototypeOf: ()=>null,
setPrototypeOf: (_, value)=>value === null
}),
()=>{
queue.length && queue.r[0]();
queue.length = 0;
},
(error = new Error('Aborted'))=>{
queue.length && queue.r[1](error);
queue.length = 0;
}
];
}
const generateRandomID = ()=>Math.random().toString(36).slice(2);

@@ -359,3 +393,3 @@

if (!isBoolean(log)) {
const { beCalled , localError , remoteError , type , requestReplay , sendLocalStack } = log;
const { beCalled, localError, remoteError, type, requestReplay, sendLocalStack } = log;
return [

@@ -380,3 +414,3 @@ undefinedToTrue(beCalled),

if (!isBoolean(strict)) {
const { methodNotFound , unknownMessage } = strict;
const { methodNotFound, unknownMessage } = strict;
return [

@@ -430,4 +464,4 @@ methodNotFound,

if (isCallbackBasedChannel(channel)) {
channel.setup((data)=>rawMessageReceiver(data).then(rawMessageSender), (data)=>{
const _ = deserialization(data);
channel.setup((data, hint)=>rawMessageReceiver(data, hint).then(rawMessageSender), (data, hint)=>{
let _ = hintedDecode(data, hint);
if (isJSONRPCObject(_)) return true;

@@ -439,7 +473,31 @@ return Promise_resolve(_).then(isJSONRPCObject);

const m = channel;
m.on && m.on((_)=>rawMessageReceiver(_).then(rawMessageSender).then((x)=>x && m.send(x)));
m.on && m.on((_, hint)=>rawMessageReceiver(_, hint).then(rawMessageSender).then((x)=>x && m.send(x)));
}
return channel;
};
const { serializer , key: logKey = 'rpc' , strict =true , log =true , parameterStructures ='by-position' , preferLocalImplementation =false , idGenerator =generateRandomID , mapError , logger , channel , thenable } = options;
const { serializer, encoder, key: deprecatedName, name, strict = true, log = true, parameterStructures: deprecatedParameterStructures, parameterStructure, preferLocalImplementation = false, idGenerator = generateRandomID, mapError, logger, channel, thenable, signal, forceSignal } = options;
// Note: we're not shorten this error message because it will be removed in the next major version.
if (serializer && encoder) throw new TypeError('Please remove serializer.');
if (name && deprecatedName) throw new TypeError('Please remove key.');
if (deprecatedParameterStructures && parameterStructure) throw new TypeError('Please remove parameterStructure.');
const paramStyle = deprecatedParameterStructures || parameterStructure || 'by-position';
const logKey = name || deprecatedName || 'rpc';
const throwIfAborted = ()=>{
signal && signal.throwIfAborted();
forceSignal && forceSignal.throwIfAborted();
};
const { encode: encodeFromOption, encodeRequest: encodeRequestFromOption, encodeResponse: encodeResponseFromOption, decode, decodeRequest, decodeResponse } = encoder || {};
const encodeRequest = encoder ? (data)=>apply(encodeRequestFromOption || encodeFromOption, encoder, [
data
]) : serializer ? (data)=>serializer.serialization(data) : Object;
const encodeResponse = encoder ? (data)=>apply(encodeResponseFromOption || encodeFromOption, encoder, [
data
]) : serializer ? (data)=>serializer.serialization(data) : Object;
const hintedDecode = encoder ? (data, hint)=>hint == 'request' ? apply(decodeRequest || decode, encoder, [
data
]) : hint == 'response' ? apply(decodeResponse || decode, encoder, [
data
]) : apply(decode, encoder, [
data
]) : serializer ? (data)=>serializer.deserialization(data) : Object;
if (thisSideImplementation instanceof Promise) awaitThisSideImplementation();

@@ -452,13 +510,15 @@ else {

const [log_beCalled, log_localError, log_remoteError, log_pretty, log_requestReplay, log_sendLocalStack] = normalizeLogOptions(log);
const { log: console_log , error: console_error = console_log , debug: console_debug = console_log , groupCollapsed: console_groupCollapsed = console_log , groupEnd: console_groupEnd = console_log , warn: console_warn = console_log } = logger || console;
const { log: console_log, error: console_error = console_log, debug: console_debug = console_log, groupCollapsed: console_groupCollapsed = console_log, groupEnd: console_groupEnd = console_log, warn: console_warn = console_log } = logger || console;
const requestContext = new Map();
onAbort(forceSignal, ()=>{
requestContext.forEach((x)=>x[1](forceSignal.reason));
requestContext.clear();
});
const onRequest = async (data)=>{
if (signal && signal.aborted || forceSignal && forceSignal.aborted) return makeErrorObject(signal && signal.reason || forceSignal && forceSignal.reason, '', data);
if (isThisSideImplementationPending) await awaitThisSideImplementation();
else {
// not pending
if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data);
}
else if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data);
let frameworkStack = '';
try {
const { params , method , id: req_id , remoteStack } = data;
const { params, method, id: req_id, remoteStack } = data;
// ? We're mapping any method starts with 'rpc.' to a Symbol.for

@@ -477,3 +537,3 @@ const key = method.startsWith('rpc.') ? Symbol.for(method) : method;

frameworkStack = removeStackHeader(new Error().stack);
const promise = new Promise((resolve)=>resolve(executor.apply(resolvedThisSideImplementationValue, args)));
const promise = new Promise((resolve)=>resolve(apply(executor, resolvedThisSideImplementationValue, args)));
if (log_beCalled) {

@@ -483,3 +543,3 @@ if (log_pretty) {

`${logKey}.%c${method}%c(${args.map(()=>'%o').join(', ')}%c)\n%o %c@${req_id}`,
'color: #d2c057',
'color:#d2c057',
'',

@@ -489,13 +549,12 @@ ...args,

promise,
'color: gray; font-style: italic;'
'color:gray;font-style:italic;'
];
if (log_requestReplay) {
// This function will be logged to the console so it must be 1 line
// prettier-ignore
const replay = ()=>{
debugger;
return executor.apply(resolvedThisSideImplementationValue, args);
return apply(executor, resolvedThisSideImplementationValue, args);
};
replay.toString = replayFunction;
logArgs.push(replay);
// this function will be logged, keep it short.
// Do not inline it, it's hard to keep it in a single line after build step.
logArgs.push(()=>replay());
}

@@ -513,3 +572,3 @@ if (remoteStack) {

if (result === AsyncCallIgnoreResponse) return;
return SuccessResponse(req_id, result);
return makeSuccessResponse(req_id, result);
} catch (e) {

@@ -532,3 +591,3 @@ return makeErrorObject(e, frameworkStack, data);

}
const { id } = data;
const { id } = data;
if (id === null || id === undefined$1 || !requestContext.has(id)) return;

@@ -538,3 +597,4 @@ const [resolve, reject, localErrorStack = ''] = requestContext.get(id);

if ('error' in data) {
reject(RecoverError(errorType, errorMessage, errorCode, // ? We use \u0430 which looks like "a" to prevent browser think "at AsyncCall" is a real stack
reject(// TODO: add a hook to customize this
RecoverError(errorType, errorMessage, errorCode, // ? We use \u0430 which looks like "a" to prevent browser think "at AsyncCall" is a real stack
remoteErrorStack + '\n \u0430t AsyncCall (rpc) \n' + localErrorStack));

@@ -546,7 +606,7 @@ } else {

};
const rawMessageReceiver = async (_)=>{
const rawMessageReceiver = async (_, hint)=>{
let data;
let result = undefined$1;
try {
data = await deserialization(_);
data = await hintedDecode(_, hint);
if (isJSONRPCObject(data)) {

@@ -568,4 +628,7 @@ return result = await handleSingleMessage(data);

if (log_localError) console_error(e, data, result);
// todo: should check before access e.stack
return ErrorResponseParseError(e, mapError || defaultErrorMapper(e && e.stack));
let stack;
try {
stack = '' + e.stack;
} catch (e) {}
return ErrorResponseParseError(e, mapError || defaultErrorMapper(stack));
}

@@ -578,9 +641,7 @@ };

if (reply.length === 0) return;
return serialization(reply);
return encodeResponse(reply);
} else {
return serialization(res);
return encodeResponse(res);
}
};
const serialization = serializer ? (x)=>serializer.serialization(x) : Object;
const deserialization = serializer ? (x)=>serializer.deserialization(x) : Object;
if (channel instanceof Promise) channelPromise = channel.then(onChannelResolved);

@@ -593,7 +654,7 @@ else onChannelResolved(channel);

};
const sendPayload = async (payload, removeQueueR = false)=>{
const sendPayload = async (payload, removeQueueR)=>{
if (removeQueueR) payload = [
...payload
];
const data = await serialization(payload);
const data = await encodeRequest(payload);
return (resolvedChannel || await channelPromise).send(data);

@@ -611,9 +672,12 @@ };

if ('method' in data) {
const r = onRequest(data);
if ('id' in data) return r;
try {
await r;
} catch (e) {}
return undefined$1 // Does not care about return result for notifications
;
if ('id' in data) {
if (!forceSignal) return onRequest(data);
return new Promise((resolve, reject)=>{
const handleForceAbort = ()=>resolve(makeErrorObject(forceSignal.reason, '', data));
onRequest(data).then(resolve, reject).finally(()=>forceSignal.removeEventListener('abort', handleForceAbort));
onAbort(forceSignal, handleForceAbort);
});
}
onRequest(data).catch(()=>{});
return; // Skip response for notifications
}

@@ -624,2 +688,3 @@ return onResponse(data);

return new Promise((resolve, reject)=>{
throwIfAborted();
let queue = undefined$1;

@@ -645,4 +710,4 @@ if (method === AsyncCallBatch) {

stack = removeStackHeader(stack);
const param = parameterStructures === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args;
const request = Request(notify ? undefined$1 : id, method, param, log_sendLocalStack ? stack : undefined$1);
const param = paramStyle === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args;
const request = makeRequest(notify ? undefined$1 : id, method, param, log_sendLocalStack ? stack : undefined$1);
if (queue) {

@@ -764,6 +829,14 @@ queue.push(request);

*/ function AsyncGeneratorCall(thisSideImplementation, options) {
if (!AsyncGeneratorPrototypeSet) {
const EmptyAsyncGenerator = async function*() {};
const AsyncGeneratorConstructor = AsyncGeneratorPrototypeSet = EmptyAsyncGenerator.constructor;
const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype;
setPrototypeOf(_AsyncGenerator, AsyncGeneratorConstructorPrototype);
const AsyncGeneratorPrototype = Object.getPrototypeOf(EmptyAsyncGenerator());
setPrototypeOf(_AsyncGenerator.prototype, AsyncGeneratorPrototype);
}
const iterators = new Map();
var _options_strict;
const [methodNotFound] = normalizeStrictOptions((_options_strict = options.strict) !== null && _options_strict !== void 0 ? _options_strict : true);
const { idGenerator =generateRandomID } = options;
const { idGenerator = generateRandomID } = options;
const findIterator = (id, next)=>{

@@ -862,9 +935,3 @@ const it = iterators.get(id);

}
// ! side effect
const EmptyAsyncGenerator = async function*() {};
const AsyncGeneratorConstructor = EmptyAsyncGenerator.constructor;
const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype;
Object_setPrototypeOf(_AsyncGenerator, AsyncGeneratorConstructorPrototype);
const AsyncGeneratorPrototype = Object.getPrototypeOf(EmptyAsyncGenerator());
Object_setPrototypeOf(_AsyncGenerator.prototype, AsyncGeneratorPrototype);
let AsyncGeneratorPrototypeSet = false;
const isFinished = async (result, cb)=>{

@@ -883,2 +950,3 @@ try {

exports.AsyncGeneratorCall = AsyncGeneratorCall;
exports.JSONEncoder = JSONEncoder;
exports.JSONSerialization = JSONSerialization;

@@ -885,0 +953,0 @@ exports.NoSerialization = NoSerialization;

@@ -1,6 +0,6 @@

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).AsyncCall={})}(this,function(t){"use strict";function e(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}class r extends Error{constructor(t,r,n,i){super(r),e(this,"name",void 0),e(this,"code",void 0),e(this,"stack",void 0),this.name=t,this.code=n,this.stack=i}}let n={},i={},o={},l={},s=[n,i,o,l],a=(t,e)=>{let r=s.indexOf(t);return e.message+=`Error ${r}: https://github.com/Jack-Works/async-call-rpc/wiki/Errors#`+r,e},c={__proto__:null,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},u="DOMException:",f=(t,e,n,i)=>{try{let o=p();if(t.startsWith(u)&&o){let r=t.slice(u.length);return new o(e,r)}if(!(t in c))return new r(t,e,n,i);{let r=new c[t](e);return r.stack=i,r.code=n,r}}catch(r){return Error(`E${n} ${t}: ${e}
${i}`)}},y=t=>(t+"").replace(/^.+\n.+\n/,""),p=()=>{try{return DOMException}catch(t){}},d=t=>"string"==typeof t,h=t=>"boolean"==typeof t,g=t=>"function"==typeof t,b=t=>"object"==typeof t&&null!==t,w="Error",m=void 0,P=Object.setPrototypeOf,O=t=>Promise.resolve(t),E=Array.isArray,$=()=>"() => replay()",k=(t,e,r,n)=>{let i={jsonrpc:"2.0",id:t,method:e,params:r,remoteStack:n};return N(i,"id"),T(i,"remoteStack"),i},v=(t,e)=>{let r={jsonrpc:"2.0",id:t,result:e};return N(r,"id"),r},j=(t,e,r,n)=>{t===m&&(t=null),Number.isNaN(e=Math.floor(e))&&(e=-1);let i={jsonrpc:"2.0",id:t,error:{code:e,message:r,data:n}};return N(i.error,"data"),i},S=(t,e)=>{let r=A({},t,e),n=r.error;return n.code=-32700,n.message="Parse error",r},x=t=>j(t,-32600,"Invalid Request"),_=t=>j(t,-32601,"Method not found"),A=(t,e,r)=>{let{id:n}=t,{code:i,message:o,data:l}=r(e,t);return j(n,i,o,l)},z=(t="",e=-1)=>r=>{let n=M("",()=>r.message),i=M(w,(t=r.constructor)=>g(t)&&t.name),o=p();o&&r instanceof o&&(i=u+r.name),(d(r)||"number"==typeof r||h(r)||"bigint"==typeof r)&&(i=w,n=r+"");let l=t?{stack:t,type:i}:{type:i};return{code:e,message:n,data:l}},C=t=>{if(!b(t)||!("jsonrpc"in t)||"2.0"!==t.jsonrpc)return!1;if("params"in t){let e=t.params;if(!E(e)&&!b(e))return!1}return!0},M=(t,e)=>{try{let r=e();if(r===m)return t;return r+""}catch(e){return t}},N=(t,e)=>{t[e]===m&&delete t[e]},T=(t,e)=>{t[e]||delete t[e]},D="AsyncCall/",R=Symbol.for(D+"ignored"),W=Symbol.for(D+"notify"),J=Symbol.for(D+"batch"),I=(t,e)=>t[e][W],q=()=>Math.random().toString(36).slice(2),F=t=>void 0===t||t,G=t=>{if("all"===t)return[!0,!0,!0,!0,!0,!0];if(!h(t)){let{beCalled:e,localError:r,remoteError:n,type:i,requestReplay:o,sendLocalStack:l}=t;return[F(e),F(r),F(n),"basic"!==i,o,l]}return t?[!0,!0,!0,!0]:[]},U=t=>{if(!h(t)){let{methodNotFound:e,unknownMessage:r}=t;return[e,r]}return[t,t]};function B(t,e){let r,n,i,s,c=!0,u=async()=>{try{r=await t}catch(t){n=t,ti("AsyncCall failed to start",t)}finally{c=!1}},p=t=>(i=t,K(t)&&t.setup(t=>ty(t).then(tp),t=>{let e=th(t);return!!C(e)||O(e).then(C)}),H(t)&&t.on&&t.on(e=>ty(e).then(tp).then(e=>e&&t.send(e))),t),{serializer:h,key:P="rpc",strict:j=!0,log:M=!0,parameterStructures:N="by-position",preferLocalImplementation:T=!1,idGenerator:D=q,mapError:I,logger:F,channel:B,thenable:L}=e;t instanceof Promise?u():(r=t,c=!1);let[Q,V]=U(j),[X,Y,Z,tt,te,tr]=G(M),{log:tn,error:ti=tn,debug:to=tn,groupCollapsed:tl=tn,groupEnd:ts=tn,warn:ta=tn}=F||console,tc=new Map,tu=async t=>{if(c)await u();else if(n)return tg(n,"",t);let e="";try{let{params:n,method:i,id:o,remoteStack:l}=t,s=i.startsWith("rpc.")?Symbol.for(i):i,a=r&&r[s];if(!g(a)){if(Q)return _(o);Y&&to("Missing method",s,t);return}let c=E(n)?n:[n];e=y(Error().stack);let u=new Promise(t=>t(a.apply(r,c)));if(X){if(tt){let t=[`${P}.%c${i}%c(${c.map(()=>"%o").join(", ")}%c)
%o %c@${o}`,"color: #d2c057","",...c,"",u,"color: gray; font-style: italic;"];if(te){let e=()=>{debugger;return a.apply(r,c)};e.toString=$,t.push(e)}l?(tl(...t),tn(l),ts()):tn(...t)}else tn(`${P}.${i}(${[...c].toString()}) @${o}`)}let f=await u;if(f===R)return;return v(o,f)}catch(r){return tg(r,e,t)}},tf=async t=>{let e="",r="",n=0,i=w;if("error"in t){let o=t.error;e=o.message,n=o.code;let l=o.data;r=b(l)&&"stack"in l&&d(l.stack)?l.stack:"<remote stack not available>",i=b(l)&&"type"in l&&d(l.type)?l.type:w,Z&&(tt?ti(`${i}: ${e}(${n}) %c@${t.id}
%c${r}`,"color: gray",""):ti(`${i}: ${e}(${n}) @${t.id}
${r}`))}let{id:o}=t;if(null===o||o===m||!tc.has(o))return;let[l,s,a=""]=tc.get(o);tc.delete(o),"error"in t?s(f(i,e,n,r+"\n аt AsyncCall (rpc) \n"+a)):l(t.result)},ty=async t=>{let e;let r=m;try{if(e=await th(t),C(e))return r=await tm(e);if(E(e)&&e.every(C)&&0!==e.length)return Promise.all(e.map(tm));if(!V)return m;{let t=e.id;return t===m&&(t=null),x(t)}}catch(t){return Y&&ti(t,e,r),S(t,I||z(t&&t.stack))}},tp=async t=>{if(t){if(!E(t))return td(t);{let e=t.filter(t=>t&&"id"in t);if(0===e.length)return;return td(e)}}},td=h?t=>h.serialization(t):Object,th=h?t=>h.deserialization(t):Object;B instanceof Promise?s=B.then(p):p(B);let tg=(t,e,r)=>(b(t)&&"stack"in t&&(t.stack=e.split("\n").reduce((t,e)=>t.replace(e+"\n",""),""+t.stack)),Y&&ti(t),A(r,t,I||z(tr?t.stack:m))),tb=async(t,e=!1)=>{e&&(t=[...t]);let r=await td(t);return(i||await s).send(r)},tw=(t,e)=>{for(let r of t)if("id"in r){let t=tc.get(r.id);t&&t[1](e)}},tm=async t=>{if("method"in t){let e=tu(t);if("id"in t)return e;try{await e}catch(t){}return m}return tf(t)},tP=(t,e,n,i=!1)=>new Promise((l,s)=>{let u=m;if(t===J&&(u=e.shift(),t=e.shift()),"symbol"==typeof t){let e=Symbol.keyFor(t)||t.description;if(e){if(e.startsWith("rpc."))t=e;else throw TypeError("Not start with rpc.")}}else if(t.startsWith("rpc."))throw a(o,TypeError());if(T&&!c&&d(t)){let n=r&&r[t];if(g(n))return l(n(...e))}let f=D();n=y(n);let p="by-name"===N&&1===e.length&&b(e[0])?e[0]:e,h=k(i?m:f,t,p,tr?n:m);if(u?(u.push(h),u.r||(u.r=[()=>tb(u,!0),t=>tw(u,t)])):tb(h).catch(s),i)return l();tc.set(f,[l,s,n])}),tO=(t,e)=>{let r={[e]:(...t)=>tP(e,t,Error().stack)}[e],n={[e]:(...t)=>tP(e,t,Error().stack,!0)}[e];return r[W]=n[W]=n,d(e)&&Object.defineProperty(tE,e,{value:r,configurable:!0}),r},tE={__proto__:new Proxy({},{get:tO})};return!1===L?tE.then=m:L===m&&Object.defineProperty(tE,"then",{configurable:!0,get(){ta(a(l,TypeError("RPC used as Promise: ")))}}),new Proxy(tE,{getPrototypeOf:()=>null,setPrototypeOf:(t,e)=>null===e,getOwnPropertyDescriptor:(t,e)=>(e in tE||tO(t,e),Object.getOwnPropertyDescriptor(tE,e))})}let H=t=>"send"in t&&g(t.send),K=t=>"setup"in t&&g(t.setup);function L(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}let Q="rpc.async-iterator.",V=Symbol.for(Q+"start"),X=Symbol.for(Q+"next"),Y=Symbol.for(Q+"return"),Z=Symbol.for(Q+"throw");class tt{async return(t){return this.d?tl(!0,t):this.c(this.r[Y](await this.i,t))}async next(t){return this.d?tl(!0):this.c(this.r[X](await this.i,t))}async throw(t){if(!this.d)return this.c(this.r[Z](await this.i,t));throw t}constructor(t,e){L(this,"r",void 0),L(this,"i",void 0),L(this,"d",void 0),L(this,"c",void 0),this.r=t,this.i=e,this.d=!1,this.c=async t=>(await to(t,()=>this.d=!0),t)}}let te=async function*(){},tr=te.constructor,tn=tr.prototype;P(tt,tn);let ti=Object.getPrototypeOf(te());P(tt.prototype,ti);let to=async(t,e)=>{try{let r=await t;r&&r.done&&e()}catch(t){}},tl=(t,e)=>({done:t,value:e});t.AsyncCall=B,t.AsyncGeneratorCall=function(t,e){var r;let o=new Map,[l]=U(null===(r=e.strict)||void 0===r||r),{idGenerator:s=q}=e,c=(t,e)=>{let r=o.get(t);if(!r){if(!l)return R;throw a(n,Error(`Iterator ${t}, `))}let i=e(r);return to(i,()=>o.delete(t)),i},u=B({async [V](e,r){let n=(await t)[e];if(!g(n)){if(!l)return R;throw TypeError(e+" is not a function")}let i=n(...r),a=s();return o.set(a,i),a},[X]:(t,e)=>c(t,t=>t.next(e)),[Y]:(t,e)=>c(t,t=>g(t.return)&&t.return(e)),[Z]:(t,e)=>c(t,t=>g(t.throw)&&t.throw(e))},e),f=(t,e)=>{if(!d(e))throw a(i,TypeError(""));let r={[e]:(...t)=>{let r=u[V](e,t);return new tt(u,r)}}[e];return Object.defineProperty(y,e,{value:r,configurable:!0}),r},y={__proto__:new Proxy({},{get:f})};return new Proxy(y,{getPrototypeOf:()=>null,setPrototypeOf:(t,e)=>null===e,getOwnPropertyDescriptor:(t,e)=>(e in y||f(t,e),Object.getOwnPropertyDescriptor(y,e))})},t.JSONSerialization=(t=[m,m],e,r="null")=>({serialization(n){if(r&&b(n)&&"result"in n&&n.result===m){let t={...n};t.result=null,"keep"===r&&(t.undef=!0),n=t}return JSON.stringify(n,t[0],e)},deserialization(e){let r=JSON.parse(e,t[1]);return b(r)&&"result"in r&&null===r.result&&"undef"in r&&!0===r.undef&&(r.result=m,delete r.undef),r}}),t.NoSerialization={serialization:t=>t,deserialization:t=>t},t.batch=function(t){let e=[],r=new Proxy({},{get(r,i){let o=(...r)=>t[J](e,i,...r);return o[W]=(...r)=>t[J][W](e,i,...r),o[W][W]=o[W],d(i)&&Object.defineProperty(n,i,{value:o,configurable:!0}),o}}),n={__proto__:r};return[new Proxy(n,{getPrototypeOf:()=>null,setPrototypeOf:(t,e)=>null===e}),()=>{e.length&&e.r[0](),e.length=0},(t=Error("Aborted"))=>{e.length&&e.r[1](t),e.length=0}]},t.notify=function(t){return g(t)?t[W]:new Proxy(t,{get:I})}});
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AsyncCall={})}(this,function(e){"use strict";var t;let r=e=>"string"==typeof e,n=e=>"boolean"==typeof e,o=e=>"function"==typeof e,i=e=>"object"==typeof e&&null!==e,l="Error",a=void 0,{setPrototypeOf:s}=Object,c=e=>Promise.resolve(e),{isArray:u}=Array,{apply:f}=Reflect;function y({keepUndefined:e="null",replacer:t,reviver:r,space:n}={}){return{encode:r=>(e&&(u(r)?r.forEach(d):d(r)),JSON.stringify(r,t,n)),decode:e=>JSON.parse(e,r)}}let d=e=>{"result"in e&&e.result===a&&(e.result=null)};(t=y||(y={})).Default=t();let p="AsyncCall/",h=Symbol.for(p+"ignored"),b=Symbol.for(p+"notify"),g=Symbol.for(p+"batch"),w=(e,t)=>e[t][b];function m(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}class P extends Error{constructor(e,t,r,n){super(t),m(this,"name",void 0),m(this,"code",void 0),m(this,"stack",void 0),this.name=e,this.code=r,this.stack=n}}let E={},O={},v={},$={},k=[E,O,v,$],S=(e,t)=>{let r=k.indexOf(e);return t.message+=`Error ${r}: https://github.com/Jack-Works/async-call-rpc/wiki/Errors#`+r,t},j={__proto__:null,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},x="DOMException:",_=(e,t,r,n)=>{try{let o;if(e.startsWith(x)&&(o=T())){let r=e.slice(x.length);return new o(t,r)}if(!(e in j))return new P(e,t,r,n);{let o=new j[e](t);return o.stack=n,o.code=r,o}}catch(o){return Error(`E${r} ${e}: ${t}
${n}`)}},N=e=>(e+"").replace(/^.+\n.+\n/,""),T=()=>{try{return DOMException}catch(e){}};function z(e,t){e&&e.addEventListener("abort",t,{once:!0})}let A=(e,t,r,n)=>{let o={jsonrpc:"2.0",id:e,method:t,params:r,remoteStack:n};return F(o,"id"),G(o,"remoteStack"),o},C=(e,t)=>{let r={jsonrpc:"2.0",id:e,result:t};return F(r,"id"),r},M=(e,t,r,n)=>{e===a&&(e=null),Number.isNaN(t=Math.floor(t))&&(t=-1);let o={jsonrpc:"2.0",id:e,error:{code:t,message:r,data:n}};return F(o.error,"data"),o},R=(e,t)=>{let r=I({},e,t),n=r.error;return n.code=-32700,n.message="Parse error",r},D=e=>M(e,-32600,"Invalid Request"),J=e=>M(e,-32601,"Method not found"),I=(e,t,r)=>{let{id:n}=e,{code:o,message:i,data:l}=r(t,e);return M(n,o,i,l)},W=(e="",t=-1)=>r=>{let n=L("",()=>r.message),i=L(l,(e=r.constructor)=>o(e)&&e.name),a=T();a&&r instanceof a&&(i=x+r.name);let s=typeof r;return("string"==s||"number"===s||"boolean"==s||"bigint"==s)&&(i=l,n=r+""),{code:t,message:n,data:e?{stack:e,type:i}:{type:i}}},q=e=>{if(!i(e)||!("jsonrpc"in e)||"2.0"!==e.jsonrpc)return!1;if("params"in e){let t=e.params;if(!u(t)&&!i(t))return!1}return!0},L=(e,t)=>{try{let r=t();if(r===a)return e;return r+""}catch(t){return e}},F=(e,t)=>{e[t]===a&&delete e[t]},G=(e,t)=>{e[t]||delete e[t]},U=()=>Math.random().toString(36).slice(2),B=e=>void 0===e||e,H=e=>{if("all"===e)return[!0,!0,!0,!0,!0,!0];if(!n(e)){let{beCalled:t,localError:r,remoteError:n,type:o,requestReplay:i,sendLocalStack:l}=e;return[B(t),B(r),B(n),"basic"!==o,i,l]}return e?[!0,!0,!0,!0]:[]},K=e=>{if(!n(e)){let{methodNotFound:t,unknownMessage:r}=e;return[t,r]}return[e,e]};function Q(e,t){let n,s,y,d,p=!0,w=async()=>{try{n=await e}catch(e){s=e,eO("AsyncCall failed to start",e)}finally{p=!1}},m=e=>(y=e,X(e)&&e.setup((e,t)=>eN(e,t).then(eT),(e,t)=>{let r=ey(e,t);return!!q(r)||c(r).then(q)}),V(e)&&e.on&&e.on((t,r)=>eN(t,r).then(eT).then(t=>t&&e.send(t))),e),{serializer:P,encoder:E,key:O,name:k,strict:j=!0,log:x=!0,parameterStructures:T,parameterStructure:M,preferLocalImplementation:L=!1,idGenerator:F=U,mapError:G,logger:B,channel:Q,thenable:Y,signal:Z,forceSignal:ee}=t;if(P&&E)throw TypeError("Please remove serializer.");if(k&&O)throw TypeError("Please remove key.");if(T&&M)throw TypeError("Please remove parameterStructure.");let et=T||M||"by-position",er=k||O||"rpc",en=()=>{Z&&Z.throwIfAborted(),ee&&ee.throwIfAborted()},{encode:eo,encodeRequest:ei,encodeResponse:el,decode:ea,decodeRequest:es,decodeResponse:ec}=E||{},eu=E?e=>f(ei||eo,E,[e]):P?e=>P.serialization(e):Object,ef=E?e=>f(el||eo,E,[e]):P?e=>P.serialization(e):Object,ey=E?(e,t)=>"request"==t?f(es||ea,E,[e]):"response"==t?f(ec||ea,E,[e]):f(ea,E,[e]):P?e=>P.deserialization(e):Object;e instanceof Promise?w():(n=e,p=!1);let[ed,ep]=K(j),[eh,eb,eg,ew,em,eP]=H(x),{log:eE,error:eO=eE,debug:ev=eE,groupCollapsed:e$=eE,groupEnd:ek=eE,warn:eS=eE}=B||console,ej=new Map;z(ee,()=>{ej.forEach(e=>e[1](ee.reason)),ej.clear()});let ex=async e=>{if(Z&&Z.aborted||ee&&ee.aborted)return ez(Z&&Z.reason||ee&&ee.reason,"",e);if(p)await w();else if(s)return ez(s,"",e);let t="";try{let{params:r,method:i,id:l,remoteStack:a}=e,s=i.startsWith("rpc.")?Symbol.for(i):i,c=n&&n[s];if(!o(c)){if(ed)return J(l);eb&&ev("Missing method",s,e);return}let y=u(r)?r:[r];t=N(Error().stack);let d=new Promise(e=>e(f(c,n,y)));if(eh){if(ew){let e=[`${er}.%c${i}%c(${y.map(()=>"%o").join(", ")}%c)
%o %c@${l}`,"color:#d2c057","",...y,"",d,"color:gray;font-style:italic;"];if(em){let t=()=>{debugger;return f(c,n,y)};e.push(()=>t())}a?(e$(...e),eE(a),ek()):eE(...e)}else eE(`${er}.${i}(${[...y].toString()}) @${l}`)}let p=await d;if(p===h)return;return C(l,p)}catch(r){return ez(r,t,e)}},e_=async e=>{let t="",n="",o=0,s=l;if("error"in e){let a=e.error;t=a.message,o=a.code;let c=a.data;n=i(c)&&"stack"in c&&r(c.stack)?c.stack:"<remote stack not available>",s=i(c)&&"type"in c&&r(c.type)?c.type:l,eg&&(ew?eO(`${s}: ${t}(${o}) %c@${e.id}
%c${n}`,"color: gray",""):eO(`${s}: ${t}(${o}) @${e.id}
${n}`))}let{id:c}=e;if(null===c||c===a||!ej.has(c))return;let[u,f,y=""]=ej.get(c);ej.delete(c),"error"in e?f(_(s,t,o,n+"\n аt AsyncCall (rpc) \n"+y)):u(e.result)},eN=async(e,t)=>{let r;let n=a;try{if(r=await ey(e,t),q(r))return n=await eM(r);if(u(r)&&r.every(q)&&0!==r.length)return Promise.all(r.map(eM));if(!ep)return a;{let e=r.id;return e===a&&(e=null),D(e)}}catch(t){let e;eb&&eO(t,r,n);try{e=""+t.stack}catch(e){}return R(t,G||W(e))}},eT=async e=>{if(e){if(!u(e))return ef(e);{let t=e.filter(e=>e&&"id"in e);if(0===t.length)return;return ef(t)}}};Q instanceof Promise?d=Q.then(m):m(Q);let ez=(e,t,r)=>(i(e)&&"stack"in e&&(e.stack=t.split("\n").reduce((e,t)=>e.replace(t+"\n",""),""+e.stack)),eb&&eO(e),I(r,e,G||W(eP?e.stack:a))),eA=async(e,t)=>{t&&(e=[...e]);let r=await eu(e);return(y||await d).send(r)},eC=(e,t)=>{for(let r of e)if("id"in r){let e=ej.get(r.id);e&&e[1](t)}},eM=async e=>"method"in e?"id"in e?ee?new Promise((t,r)=>{let n=()=>t(ez(ee.reason,"",e));ex(e).then(t,r).finally(()=>ee.removeEventListener("abort",n)),z(ee,n)}):ex(e):void ex(e).catch(()=>{}):e_(e),eR=(e,t,l,s=!1)=>new Promise((c,u)=>{en();let f=a;if(e===g&&(f=t.shift(),e=t.shift()),"symbol"==typeof e){let t=Symbol.keyFor(e)||e.description;if(t){if(t.startsWith("rpc."))e=t;else throw TypeError("Not start with rpc.")}}else if(e.startsWith("rpc."))throw S(v,TypeError());if(L&&!p&&r(e)){let r=n&&n[e];if(o(r))return c(r(...t))}let y=F();l=N(l);let d="by-name"===et&&1===t.length&&i(t[0])?t[0]:t,h=A(s?a:y,e,d,eP?l:a);if(f?(f.push(h),f.r||(f.r=[()=>eA(f,!0),e=>eC(f,e)])):eA(h).catch(u),s)return c();ej.set(y,[c,u,l])}),eD=(e,t)=>{let n={[t]:(...e)=>eR(t,e,Error().stack)}[t],o={[t]:(...e)=>eR(t,e,Error().stack,!0)}[t];return n[b]=o[b]=o,r(t)&&Object.defineProperty(eJ,t,{value:n,configurable:!0}),n},eJ={__proto__:new Proxy({},{get:eD})};return!1===Y?eJ.then=a:Y===a&&Object.defineProperty(eJ,"then",{configurable:!0,get(){eS(S($,TypeError("RPC used as Promise: ")))}}),new Proxy(eJ,{getPrototypeOf:()=>null,setPrototypeOf:(e,t)=>null===t,getOwnPropertyDescriptor:(e,t)=>(t in eJ||eD(e,t),Object.getOwnPropertyDescriptor(eJ,t))})}let V=e=>"send"in e&&o(e.send),X=e=>"setup"in e&&o(e.setup);function Y(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}let Z="rpc.async-iterator.",ee=Symbol.for(Z+"start"),et=Symbol.for(Z+"next"),er=Symbol.for(Z+"return"),en=Symbol.for(Z+"throw");class eo{async return(e){return this.d?ea(!0,e):this.c(this.r[er](await this.i,e))}async next(e){return this.d?ea(!0):this.c(this.r[et](await this.i,e))}async throw(e){if(!this.d)return this.c(this.r[en](await this.i,e));throw e}constructor(e,t){Y(this,"r",void 0),Y(this,"i",void 0),Y(this,"d",void 0),Y(this,"c",void 0),this.r=e,this.i=t,this.d=!1,this.c=async e=>(await el(e,()=>this.d=!0),e)}}let ei=!1,el=async(e,t)=>{try{let r=await e;r&&r.done&&t()}catch(e){}},ea=(e,t)=>({done:e,value:t});e.AsyncCall=Q,e.AsyncGeneratorCall=function(e,t){var n;if(!ei){let e=async function*(){};s(eo,(ei=e.constructor).prototype);let t=Object.getPrototypeOf(e());s(eo.prototype,t)}let i=new Map,[l]=K(null===(n=t.strict)||void 0===n||n),{idGenerator:a=U}=t,c=(e,t)=>{let r=i.get(e);if(!r){if(!l)return h;throw S(E,Error(`Iterator ${e}, `))}let n=t(r);return el(n,()=>i.delete(e)),n},u=Q({async [ee](t,r){let n=(await e)[t];if(!o(n)){if(!l)return h;throw TypeError(t+" is not a function")}let s=n(...r),c=a();return i.set(c,s),c},[et]:(e,t)=>c(e,e=>e.next(t)),[er]:(e,t)=>c(e,e=>o(e.return)&&e.return(t)),[en]:(e,t)=>c(e,e=>o(e.throw)&&e.throw(t))},t),f=(e,t)=>{if(!r(t))throw S(O,TypeError(""));let n={[t]:(...e)=>{let r=u[ee](t,e);return new eo(u,r)}}[t];return Object.defineProperty(y,t,{value:n,configurable:!0}),n},y={__proto__:new Proxy({},{get:f})};return new Proxy(y,{getPrototypeOf:()=>null,setPrototypeOf:(e,t)=>null===t,getOwnPropertyDescriptor:(e,t)=>(t in y||f(e,t),Object.getOwnPropertyDescriptor(y,t))})},e.JSONEncoder=y,e.JSONSerialization=(e=[a,a],t,r="null")=>({serialization(n){if(r&&i(n)&&"result"in n&&n.result===a){let e={...n};e.result=null,"keep"===r&&(e.undef=!0),n=e}return JSON.stringify(n,e[0],t)},deserialization(t){let r=JSON.parse(t,e[1]);return i(r)&&"result"in r&&null===r.result&&"undef"in r&&!0===r.undef&&(r.result=a,delete r.undef),r}}),e.NoSerialization={serialization:e=>e,deserialization:e=>e},e.batch=function(e){let t=[],n={__proto__:new Proxy({},{get(o,i){let l=(...r)=>e[g](t,i,...r);return l[b]=(...r)=>e[g][b](t,i,...r),l[b][b]=l[b],r(i)&&Object.defineProperty(n,i,{value:l,configurable:!0}),l}})};return[new Proxy(n,{getPrototypeOf:()=>null,setPrototypeOf:(e,t)=>null===t}),()=>{t.length&&t.r[0](),t.length=0},(e=Error("Aborted"))=>{t.length&&t.r[1](e),t.length=0}]},e.notify=function(e){return o(e)?e[b]:new Proxy(e,{get:w})}});
//# sourceMappingURL=full.min.js.map
{
"name": "async-call-rpc",
"packageManager": "pnpm@8.3.1",
"version": "6.3.1",
"packageManager": "pnpm@8.15.1",
"version": "6.4.0",
"description": "A lightweight JSON RPC server & client",

@@ -11,25 +11,50 @@ "main": "out/base.js",

".": {
"types": "./out/base.d.ts",
"require": "./out/base.js",
"import": "./out/base.mjs"
"require": {
"types": "./out/base.d.ts",
"default": "./out/base.js"
},
"import": {
"types": "./out/base.d.ts",
"default": "./out/base.mjs"
}
},
"./full": {
"types": "./out/full.d.ts",
"require": "./out/full.js",
"import": "./out/full.mjs"
"require": {
"types": "./out/full.d.ts",
"default": "./out/full.js"
},
"import": {
"types": "./out/full.d.ts",
"default": "./out/full.mjs"
}
},
"./full.min": {
"types": "./out/full.d.ts",
"require": "./out/full.min.js",
"import": "./out/full.min.mjs"
"require": {
"types": "./out/full.d.ts",
"default": "./out/full.min.js"
},
"import": {
"types": "./out/full.d.ts",
"default": "./out/full.min.mjs"
}
},
"./base": {
"types": "./out/base.d.ts",
"require": "./out/base.js",
"import": "./out/base.mjs"
"require": {
"types": "./out/base.d.ts",
"default": "./out/base.js"
},
"import": {
"types": "./out/base.d.ts",
"default": "./out/base.mjs"
}
},
"./base.min": {
"types": "./out/base.d.ts",
"require": "./out/base.min.js",
"import": "./out/base.min.mjs"
"require": {
"types": "./out/base.d.ts",
"default": "./out/base.min.js"
},
"import": {
"types": "./out/base.d.ts",
"default": "./out/base.min.mjs"
}
},

@@ -68,26 +93,27 @@ "./utils/*": "./utils/*"

"devDependencies": {
"@changesets/cli": "^2.26.1",
"@microsoft/api-documenter": "^7.22.5",
"@microsoft/api-extractor": "^7.34.9",
"@changesets/cli": "^2.27.1",
"@microsoft/api-documenter": "^7.23.23",
"@microsoft/api-extractor": "^7.40.1",
"@msgpack/msgpack": "3.0.0-beta2",
"@swc/core": "^1.3.59",
"@types/node": "^20.2.1",
"@types/ws": "^8.5.4",
"@vitest/coverage-c8": "^0.31.1",
"@vitest/ui": "^0.31.1",
"@swc/core": "^1.4.0",
"@types/node": "^20.11.17",
"@types/ws": "^8.5.10",
"@vitest/coverage-c8": "^0.33.0",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"async-call-rpc": "link:",
"bson": "^5.3.0",
"c8": "^7.13.0",
"bson": "^6.3.0",
"c8": "^9.1.0",
"jest-file-snapshot": "^0.5.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"pretty-format": "^29.5.0",
"rimraf": "^5.0.1",
"rollup": "^3.22.0",
"rollup-plugin-swc3": "^0.8.1",
"serve": "^14.2.0",
"typescript": "^5.0.4",
"vite": "^4.3.8",
"vitest": "^0.31.1",
"ws": "^8.13.0"
"prettier": "^3.2.5",
"pretty-format": "^29.7.0",
"rimraf": "^5.0.5",
"rollup": "^4.10.0",
"rollup-plugin-swc3": "^0.11.0",
"serve": "^14.2.1",
"typescript": "^5.3.3",
"vite": "^5.1.1",
"vitest": "^1.2.2",
"ws": "^8.16.0"
},

@@ -111,3 +137,4 @@ "files": [

"doc:md": "api-documenter markdown -o docs -i dist/api-extractor",
"doc": "run-s doc:api doc:md",
"doc:post": "git checkout HEAD -- ./docs/index.html ./docs/_config.yml",
"doc": "run-s doc:api doc:md doc:post",
"start": "run-p watch:rollup watch:tsc watch:test",

@@ -114,0 +141,0 @@ "test": "vitest --coverage"

# Async Call
`async-call-rpc` is a [JSON RPC](https://www.jsonrpc.org/specification) server and client written in TypeScript for any ES6+ environment.
`async-call-rpc` is a [JSON RPC](https://www.jsonrpc.org/specification) server and client written in TypeScript for any ECMAScript environment.

@@ -16,8 +16,8 @@ [![Code coverage](https://codecov.io/gh/Jack-Works/async-call-rpc/branch/main/graph/badge.svg)](https://codecov.io/gh/Jack-Works/async-call-rpc)

- [The first concept: `channel`](#the-first-concept-messagechannel)
- [Installation](#installation)
- [`channel`, how to communicate](#channel)
- [`encoder`, how to use complex data types](#encoder)
- [Example](#example)
- [Notifications and Batch requests](#notifications-and-batch-requests)
- [Installation](#installation)
- [Entries](#entries)
- [Utils available if both server and client are created by this library](#utils-available-if-both-server-and-client-are-created-by-this-library)
- [Package entries](#package-entries)
- [Builtin `channels` (including WebSocket)](#builtin-channels)

@@ -30,33 +30,64 @@ - [Implemented JSON RPC internal methods](#implemented-json-rpc-internal-methods)

- Zero dependencies!
- Running in any ES6+ environment (+`globalThis`), no requirement on any Web or Node API
- Simple to define a server and simple to use as a client
- Running in any ES6 environment, no requirement on any Web or Node API
- Simple to define a RPC server and simple to use as a RPC client
- Full TypeScript support
- Support custom serializer to pass complex data types
- Support async generator (Require both server and client supports 4 JSON RPC internal methods, and `Symbol.asyncIterator`, `(async function* () {}).constructor.prototype` available)
- Use a custom encoder to communicate with complex data types
- Support async generator (Require both server and client supports [4 JSON RPC internal methods listed below](#implemented-json-rpc-internal-methods) and async generator exists in the environment)
## Cautions
- NOT support ECMAScript 5 (ES6 `Proxy` is the core of this library)
- This package is shipping ECMAScript 2018 syntax (including `async function`).
- The async generator mode might leak memory on the server. Use it by your caution.
- This package is shipping ECMAScript 2018 syntax (`async function`).
- The async generator support leaks memory on the server. Use it with caution.
- NOT support ES5.
- NOT support JSON RPC 1.0
## The first concept: `channel`
## Installation
<a id="channel"></a>
### Install via npm
The `channel` is the only thing you need to learn to use this library.
> npm i async-call-rpc
>
> yarn add async-call-rpc
>
> pnpm i async-call-rpc
This library is designed to not rely on any specific platform. Only require things defined in the ECMAScript specification.
In the ES spec, there is no I/O related API so it's impossible to communicate with the outer world.
### Import from browser or Deno
You need to implement one of the following interfaces:
You can access <https://www.jsdelivr.com/package/npm/async-call-rpc?path=out> to get the latest URL and [SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
- [CallbackBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.callbackbasedchannel.html), generally used in the server. [Example](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/websocket.server.ts).
- [EventBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.eventbasedchannel.html), generally used in the client. [Example](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/websocket.client.ts)
```js
import { AsyncCall } from 'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'
```
There are some [built-in channel](#builtin-channels) you can simplify the usage.
### UMD
```html
<script src="https://cdn.jsdelivr.net/npm/async-call-rpc@2.0.1/out/base.js"></script>
<script>
const { AsyncCall } = globalThis.AsyncCall
</script>
```
### Other JS environments
Load the `out/base.mjs` (ES Module) or `out/base.js` (UMD, CommonJS, or AMD) to your project.
## `channel`
To communicate with the server/client, you need to implement one of the following interfaces:
- [CallbackBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.callbackbasedchannel.html), generally used in the server. [Example: WebSocket on Node](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/websocket.server.ts).
- [EventBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.eventbasedchannel.html), generally used in the client. [Example: WebSocket on Web](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/websocket.client.ts)
There are some [built-in channels](#builtin-channels) you can simplify the usage.
The following document will assume you have defined your `channel`.
## `encoder`
This library does not have any opinionated data transmitting format. You need to implement one of the following interfaces:
- [IsomorphicEncoder](https://jack-works.github.io/async-call-rpc/async-call-rpc.isomorphicencoder.html). [Example: JSONEncoder](https://jack-works.github.io/async-call-rpc/async-call-rpc.jsonencoder.html)
- [IsomorphicEncoderFull](https://jack-works.github.io/async-call-rpc/async-call-rpc.isomorphicencoderfull.html)
## Example

@@ -75,3 +106,3 @@

import { AsyncCall } from 'async-call-rpc'
import * as server from './server'
import * as server from './server.ts'
// create a server

@@ -85,2 +116,3 @@ AsyncCall(server, { channel })

import { AsyncCall } from 'async-call-rpc'
import type * as server from './server.ts'
const server = AsyncCall<typeof server>({}, { channel })

@@ -90,14 +122,7 @@ server.add(2, 40).then(console.log) // 42

### Isomorphic API
You can notice from the above example,
define a server is using `AsyncCall(serverImplementation, opt)`,
define a client is using `AsyncCall<typeof serverImplementation>({}, opt)`.
So it is possible to define a server and a client at the same time.
## Notifications and Batch requests
AsyncCall can send [Notifications](https://www.jsonrpc.org/specification#notification).
AsyncCall can send [notifications](https://www.jsonrpc.org/specification#notification).
Using notifications means results or remote errors will be dropped. Local errors won't be omitted, e.g. serializer error or network error.
Using notifications means results or remote errors are never sent back. Local errors will not be omitted, e.g. encoder errors or network errors.

@@ -107,3 +132,3 @@ ```ts

const server = notify(AsyncCall<typeof server>({}, { channel }))
server.online().then(console.log) // undefined
server.add(1, 2).then(console.log) // undefined
```

@@ -123,44 +148,13 @@

const d = server.req1() // pending
drop() // to drop all pending requests (and corresponding Promises)
drop() // to drop all pending requests (and reject corresponding Promises)
// d rejected
```
## Installation
## Package entries
### Install through npm
This library has 2 entries. `base` and `full`. `base` is the default entry point. The `full` version includes the `AsyncGeneratorCall` but the `base` version doesn't.
> npm i async-call-rpc
> yarn add async-call-rpc
### Import from browser or Deno
You can access https://www.jsdelivr.com/package/npm/async-call-rpc?path=out to get the latest URL and SRI.
(Supports type definition for deno out-of-box!)
```js
import { AsyncCall } from 'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'
```
### UMD
```html
<script src="https://cdn.jsdelivr.net/npm/async-call-rpc@2.0.1/out/base.js"></script>
<script>
const { AsyncCall } = globalThis.AsyncCall
</script>
```
### In other JS environment
Load the `out/base.mjs` (ES Module) or `out/base.js` (UMD, CommonJS or AMD) to your project.
## Entries
This library has 2 entry. `base` and `full`. `base` is the default entry point. The `full` version includes the `AsyncGeneratorCall` but the base version doesn't.
### Browser / Deno
Please check out https://www.jsdelivr.com/package/npm/async-call-rpc?path=out
Please check out <https://www.jsdelivr.com/package/npm/async-call-rpc?path=out>

@@ -172,7 +166,7 @@ ### Node:

require('async-rpc-call/full') // or
import * as RPC from 'async-rpc-call/full'
import { } from 'async-rpc-call/full'
// Base version
require('async-rpc-call/base') // or
import * as RPC from 'async-rpc-call/base'
require('async-rpc-call') // or
import { } from 'async-rpc-call'
```

@@ -182,3 +176,3 @@

They're not part of the core library but provided as utils to increase usability.
They're not part of the core library but are provided as utils to increase usability.

@@ -214,4 +208,2 @@ ### (Node) WebSocket

> ⚠️ Broadcast Channel is not supported by Safari yet ⚠️
| | Server & Client |

@@ -226,4 +218,2 @@ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

> ⚠️ Import a ES Module in a Web Worker is only supported by Chrome yet! ⚠️
| | Host & Worker |

@@ -236,34 +226,6 @@ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Main frame: `new WorkerChannel(new Worker(...))`
Main thread: `new WorkerChannel(new Worker(...))`
Worker: `new WorkerChannel()`
## Builtin serializers
### (Web, Deno and Node) BSON
| | Server |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entry point Node | `async-call-rpc/utils/node/bson.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/bson.ts) |
| Entry point Browser/Deno | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/bson.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/bson.ts) |
| Dependencies | [bson](https://npmjs.com/bson) |
### (Web, Deno and Node) Msgpack
| | Server |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entry point Node | `async-call-rpc/utils/node/msgpack.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/msgpack.ts) |
| Entry point Browser/Deno | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/msgpack.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/msgpack.ts) |
| Dependencies | [@msgpack/msgpack](https://npmjs.com/@msgpack/msgpack) |
| Example (Node) | [./examples/node.websocket.server.js](https://github.com/Jack-Works/async-call-rpc/blob/main/examples/node.websocket.server.js) |
| Example (Deno) | [./examples/deno.websocket.server.ts](https://github.com/Jack-Works/async-call-rpc/blob/main/examples/deno.websocket.server.ts) |
| Example (Web) | [./examples/browser.websocket.client.js](https://github.com/Jack-Works/async-call-rpc/blob/main/examples/browser.websocket.client.js) |
## Utils available if both server and client are created by this library
AsyncCall has some non-standard extensions to the JSON RPC specification that can help the library easier to use. Those features aren't enabled by default.
- Send call stack of Error response or send call stack of caller's request. See [remoteStack on Request object](#remotestack-on-request-object)
- Try to keep the "undefined" result when using JSONSerialization. See ["undef" on response object](#undef-on-response-object)
## Implemented JSON RPC internal methods

@@ -276,9 +238,9 @@

// These 4 methods represent the Async Iterator protocol in ECMAScript
// this method starts an async iterator, return the id
//This method starts an async iterator, returns the id
'rpc.async-iterator.start'(method: string, params: unknown[]): Promise<string>
// this method executes `next` method on the previous iterator started by `rpc.async-iterator.start`
//This method executes the `next` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.next'(id: string, value: unknown): Promise<IteratorResult<unknown>>
// this method executes `return` method on the previous iterator started by `rpc.async-iterator.start`
//This method executes the `return` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.return'(id: string, value: unknown): Promise<IteratorResult<unknown>>
// this method executes `throw` method on the previous iterator started by `rpc.async-iterator.start`
//This method executes the `throw` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.throw'(id: string, value: unknown): Promise<IteratorResult<unknown>>

@@ -290,3 +252,3 @@ }

### remoteStack on Request object
### remoteStack on the Request object

@@ -299,3 +261,3 @@ This library can send the client the call stack to the server to make the logger better.

interface JSONRPC_Request_object {
// This property include the caller's stack.
// This property includes the caller's stack.
remoteStack?: string

@@ -307,3 +269,3 @@ }

This is a non-standard property appears when using JSONSerialization due to JSON doesn't support `undefined`. It's a hint to the client, that the result is `undefined`.
This is a non-standard property that appears when using the deprecated JSONSerialization due to JSON doesn't support `undefined`. It's a hint to the client, that the result is `undefined`.

@@ -328,3 +290,3 @@ This behavior is controlled by the 3rd parameter of [JSONSerialization(replacerAndReceiver?, space?, undefinedKeepingBehavior?: false | "keep" | "null" = "null")](https://jack-works.github.io/async-call-rpc/async-call-rpc.jsonserialization.html). Default to `"null"`. To turn on this feature to "keep" undefined values, change the 3rd option to "keep".

interface JSONRPC_Error_object {
// This property will help client to build a better Error object.
// This property will help the client to build a better Error object.
data?: {

@@ -331,0 +293,0 @@ stack?: string

@@ -9,3 +9,3 @@ /**

import { generateRandomID } from './utils/generateRandomID.js'
import { isFunction, isString, Object_setPrototypeOf } from './utils/constants.js'
import { isFunction, isString, setPrototypeOf } from './utils/constants.js'
import {

@@ -118,2 +118,10 @@ Err_Cannot_find_a_running_iterator_with_given_ID,

): AsyncGeneratorVersionOf<OtherSideImplementedFunctions> {
if (!AsyncGeneratorPrototypeSet) {
const EmptyAsyncGenerator = async function* () {}
const AsyncGeneratorConstructor = (AsyncGeneratorPrototypeSet = EmptyAsyncGenerator.constructor)
const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype
setPrototypeOf(_AsyncGenerator, AsyncGeneratorConstructorPrototype)
const AsyncGeneratorPrototype = Object.getPrototypeOf(EmptyAsyncGenerator())
setPrototypeOf(_AsyncGenerator.prototype, AsyncGeneratorPrototype)
}
const iterators = new Map<string | number, Iter>()

@@ -194,3 +202,6 @@ const [methodNotFound] = normalizeStrictOptions(options.strict ?? true)

*/
constructor(private r: AsyncGeneratorInternalMethods, private i: Promise<string>) {}
constructor(
private r: AsyncGeneratorInternalMethods,
private i: Promise<string>,
) {}
async return(val: unknown) {

@@ -211,9 +222,3 @@ if (this.d) return makeIteratorResult(true, val)

}
// ! side effect
const EmptyAsyncGenerator = async function* () {}
const AsyncGeneratorConstructor = EmptyAsyncGenerator.constructor
const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype
Object_setPrototypeOf(_AsyncGenerator, AsyncGeneratorConstructorPrototype)
const AsyncGeneratorPrototype = Object.getPrototypeOf(EmptyAsyncGenerator())
Object_setPrototypeOf(_AsyncGenerator.prototype, AsyncGeneratorPrototype)
let AsyncGeneratorPrototypeSet: unknown = false

@@ -220,0 +225,0 @@ const isFinished = async (result: IterResult | undefined | false, cb: () => void) => {

export * from './types.js'
export type { _IgnoreResponse } from './core/notify.js'
export { JSONSerialization, NoSerialization } from './utils/serialization.js'
export { JSONEncoder, type JSONEncoderOptions } from './utils/encoder.js'
export { notify } from './core/notify.js'

@@ -8,9 +9,6 @@ export { batch } from './core/batch.js'

import {
Request,
type Response,
makeRequest,
ErrorResponseMapped,
SuccessResponse,
makeSuccessResponse,
isJSONRPCObject,
isObject,
ErrorResponse,
defaultErrorMapper,

@@ -27,2 +25,3 @@ ErrorResponseMethodNotFound,

Err_Then_is_accessed_on_local_implementation_Please_explicitly_mark_if_it_is_thenable_in_the_options,
onAbort,
} from './utils/error.js'

@@ -39,4 +38,12 @@ import { generateRandomID } from './utils/generateRandomID.js'

AsyncVersionOf,
Request,
Response,
SuccessResponse,
ErrorResponse,
IsomorphicEncoderFull,
IsomorphicEncoder,
Requests,
Responses,
} from './types.js'
import { ERROR, isArray, isFunction, isString, Promise_resolve, replayFunction, undefined } from './utils/constants.js'
import { apply, ERROR, isArray, isFunction, isObject, isString, Promise_resolve, undefined } from './utils/constants.js'

@@ -65,2 +72,4 @@ /**

): AsyncVersionOf<OtherSideImplementedFunctions> {
type Hint = 'request' | 'response' | undefined
let isThisSideImplementationPending = true

@@ -87,5 +96,6 @@ let resolvedThisSideImplementationValue: unknown

channel.setup(
(data) => rawMessageReceiver(data).then(rawMessageSender),
(data) => {
const _ = deserialization(data)
(data, hint) => rawMessageReceiver(data, hint).then(rawMessageSender),
(data, hint) => {
let _ = hintedDecode(data, hint)
if (isJSONRPCObject(_)) return true

@@ -99,4 +109,4 @@ return Promise_resolve(_).then(isJSONRPCObject)

m.on &&
m.on((_) =>
rawMessageReceiver(_)
m.on((_, hint) =>
rawMessageReceiver(_, hint)
.then(rawMessageSender)

@@ -111,6 +121,9 @@ .then((x) => x && m.send!(x)),

serializer,
key: logKey = 'rpc',
encoder,
key: deprecatedName,
name,
strict = true,
log = true,
parameterStructures = 'by-position',
parameterStructures: deprecatedParameterStructures,
parameterStructure,
preferLocalImplementation = false,

@@ -122,4 +135,50 @@ idGenerator = generateRandomID,

thenable,
signal,
forceSignal,
} = options
// Note: we're not shorten this error message because it will be removed in the next major version.
if (serializer && encoder) throw new TypeError('Please remove serializer.')
if (name && deprecatedName) throw new TypeError('Please remove key.')
if (deprecatedParameterStructures && parameterStructure) throw new TypeError('Please remove parameterStructure.')
const paramStyle = deprecatedParameterStructures || parameterStructure || 'by-position'
const logKey = name || deprecatedName || 'rpc'
const throwIfAborted = () => {
signal && signal.throwIfAborted()
forceSignal && forceSignal.throwIfAborted()
}
const {
encode: encodeFromOption,
encodeRequest: encodeRequestFromOption,
encodeResponse: encodeResponseFromOption,
decode,
decodeRequest,
decodeResponse,
} = (encoder || {}) as IsomorphicEncoder & IsomorphicEncoderFull
const encodeRequest: (data: Requests | Responses) => any = encoder
? (data) => apply(encodeRequestFromOption || encodeFromOption, encoder, [data])
: serializer
? (data) => serializer.serialization(data)
: Object
const encodeResponse: (data: Requests | Responses) => any = encoder
? (data) => apply(encodeResponseFromOption || encodeFromOption, encoder, [data])
: serializer
? (data) => serializer.serialization(data)
: Object
const hintedDecode: (data: unknown, hint: Hint) => unknown = encoder
? (data, hint) =>
hint == 'request'
? apply(decodeRequest || decode, encoder, [data])
: hint == 'response'
? apply(decodeResponse || decode, encoder, [data])
: apply(decode, encoder, [data])
: serializer
? (data) => serializer.deserialization(data)
: Object
if (thisSideImplementation instanceof Promise) awaitThisSideImplementation()

@@ -144,8 +203,14 @@ else {

const requestContext = new Map<string | number, PromiseParam>()
onAbort(forceSignal, () => {
requestContext.forEach((x) => x[1](forceSignal!.reason))
requestContext.clear()
})
const onRequest = async (data: Request): Promise<Response | undefined> => {
if ((signal && signal.aborted) || (forceSignal && forceSignal.aborted))
return makeErrorObject((signal && signal.reason) || (forceSignal && forceSignal.reason), '', data)
if (isThisSideImplementationPending) await awaitThisSideImplementation()
else {
// not pending
if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data)
}
// TODO: in next major version we should not send this message since it might contain sensitive info.
else if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data)
let frameworkStack: string = ''

@@ -156,4 +221,3 @@ try {

const key = (method.startsWith('rpc.') ? Symbol.for(method) : method) as keyof object
const executor: unknown =
resolvedThisSideImplementationValue && (resolvedThisSideImplementationValue as any)[key]
const executor: unknown = resolvedThisSideImplementationValue && resolvedThisSideImplementationValue[key]
if (!isFunction(executor)) {

@@ -167,3 +231,5 @@ if (!banMethodNotFound) {

frameworkStack = removeStackHeader(new Error().stack)
const promise = new Promise((resolve) => resolve(executor.apply(resolvedThisSideImplementationValue, args)))
const promise = new Promise((resolve) =>
resolve(apply(executor, resolvedThisSideImplementationValue, args)),
)
if (log_beCalled) {

@@ -173,3 +239,3 @@ if (log_pretty) {

`${logKey}.%c${method}%c(${args.map(() => '%o').join(', ')}%c)\n%o %c@${req_id}`,
'color: #d2c057',
'color:#d2c057',
'',

@@ -179,10 +245,12 @@ ...args,

promise,
'color: gray; font-style: italic;',
'color:gray;font-style:italic;',
]
if (log_requestReplay) {
// This function will be logged to the console so it must be 1 line
// prettier-ignore
const replay = () => { debugger; return executor.apply(resolvedThisSideImplementationValue, args) }
replay.toString = replayFunction
logArgs.push(replay)
const replay = () => {
debugger
return apply(executor, resolvedThisSideImplementationValue, args)
}
// this function will be logged, keep it short.
// Do not inline it, it's hard to keep it in a single line after build step.
logArgs.push(() => replay())
}

@@ -198,3 +266,3 @@ if (remoteStack) {

if (result === AsyncCallIgnoreResponse) return
return SuccessResponse(req_id, result)
return makeSuccessResponse(req_id, result)
} catch (e) {

@@ -236,2 +304,3 @@ return makeErrorObject(e, frameworkStack, data)

reject(
// TODO: add a hook to customize this
RecoverError(

@@ -250,7 +319,10 @@ errorType,

}
const rawMessageReceiver = async (_: unknown): Promise<undefined | Response | (Response | undefined)[]> => {
const rawMessageReceiver = async (
_: unknown,
hint: Hint,
): Promise<undefined | Response | (Response | undefined)[]> => {
let data: unknown
let result: Response | undefined = undefined
try {
data = await deserialization(_)
data = await hintedDecode(_, hint)
if (isJSONRPCObject(data)) {

@@ -272,4 +344,7 @@ return (result = await handleSingleMessage(data))

if (log_localError) console_error(e, data, result)
// todo: should check before access e.stack
return ErrorResponseParseError(e, mapError || defaultErrorMapper(e && (e as any).stack))
let stack: string | undefined
try {
stack = '' + (e as any).stack
} catch {}
return ErrorResponseParseError(e, mapError || defaultErrorMapper(stack))
}

@@ -280,11 +355,9 @@ }

if (isArray(res)) {
const reply = res.filter((x) => x && 'id' in x)
const reply = res.filter((x): x is Response => (x && 'id' in x) as boolean)
if (reply.length === 0) return
return serialization(reply)
return encodeResponse(reply)
} else {
return serialization(res)
return encodeResponse(res)
}
}
const serialization = serializer ? (x: unknown) => serializer.serialization(x) : Object
const deserialization = serializer ? (x: unknown) => serializer.deserialization(x) : Object

@@ -303,5 +376,5 @@ if (channel instanceof Promise) channelPromise = channel.then(onChannelResolved)

const sendPayload = async (payload: unknown, removeQueueR = false) => {
const sendPayload = async (payload: Requests | BatchQueue, removeQueueR?: true) => {
if (removeQueueR) payload = [...(payload as BatchQueue)]
const data = await serialization(payload)
const data = await encodeRequest(payload)
return (resolvedChannel || (await channelPromise))!.send!(data)

@@ -321,8 +394,14 @@ }

if ('method' in data) {
const r = onRequest(data)
if ('id' in data) return r
try {
await r
} catch {}
return undefined // Does not care about return result for notifications
if ('id' in data) {
if (!forceSignal) return onRequest(data)
return new Promise((resolve, reject) => {
const handleForceAbort = () => resolve(makeErrorObject(forceSignal.reason, '', data))
onRequest(data)
.then(resolve, reject)
.finally(() => forceSignal.removeEventListener('abort', handleForceAbort))
onAbort(forceSignal, handleForceAbort)
})
}
onRequest(data).catch(() => {})
return // Skip response for notifications
}

@@ -333,2 +412,3 @@ return onResponse(data) as Promise<undefined>

return new Promise<void>((resolve, reject) => {
throwIfAborted()
let queue: BatchQueue | undefined = undefined

@@ -356,4 +436,4 @@ if (method === AsyncCallBatch) {

stack = removeStackHeader(stack)
const param = parameterStructures === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args
const request = Request(
const param = paramStyle === 'by-name' && args.length === 1 && isObject(args[0]) ? args[0] : args
const request = makeRequest(
notify ? undefined : id,

@@ -366,3 +446,3 @@ method as string,

queue.push(request)
if (!queue.r) queue.r = [() => sendPayload(queue, true), (e) => rejectsQueue(queue!, e)]
if (!queue.r) queue.r = [() => sendPayload(queue!, true), (e) => rejectsQueue(queue!, e)]
} else sendPayload(request).catch(reject)

@@ -369,0 +449,0 @@ if (notify) return resolve()

import { isString } from '../utils/constants.js'
import { AsyncCallBatch, AsyncCallNotify } from '../utils/internalSymbol.js'
import type { Request } from '../utils/jsonrpc.js'
import type { Request } from '../types.js'
/**

@@ -22,2 +22,3 @@ * Wrap the AsyncCall instance to use batch call.

*/
// TODO: use private field in the future.
export function batch<T extends object>(asyncCallInstance: T): [T, () => void, (error?: unknown) => void] {

@@ -35,3 +36,2 @@ const queue: BatchQueue = []

asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args)
// @ts-expect-error
f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify]

@@ -38,0 +38,0 @@ isString(p) && Object.defineProperty(methodContainer, p, { value: f, configurable: true })

/**
* ! This file MUST NOT contain any import statement.
* ! This file is part of public API of this package (for Deno users).
*/
/**
* This interface represents a "on message" - "send response" model.

@@ -19,3 +14,3 @@ * @remarks

*/
on(listener: (data: Data) => void): void | (() => void)
on(listener: (data: Data, hint?: 'request' | 'response' | undefined) => void): void | (() => void)
/**

@@ -25,3 +20,3 @@ * Send the data to the remote side.

*/
send(data: Data): void
send(data: Data): void | Promise<void>
}

@@ -44,20 +39,28 @@

setup(
jsonRPCHandlerCallback: (jsonRPCPayload: unknown) => Promise<unknown | undefined>,
isValidJSONRPCPayload: (data: unknown) => boolean | Promise<boolean>,
jsonRPCHandlerCallback: (
jsonRPCPayload: unknown,
hint?: undefined | 'request' | 'response',
) => Promise<unknown | undefined>,
isValidJSONRPCPayload: (data: unknown, hint?: undefined | 'request' | 'response') => boolean | Promise<boolean>,
): (() => void) | void
}
/**
* Log options of AsyncCall
* Log options
* @remarks
* This option controls how AsyncCall should log RPC calls to the console.
* This option controls how AsyncCall log requests to the console.
* @public
* @privateRemarks
* TODO: rename to AsyncCallLogOptions
* TODO: split to server log and client log
*/
export interface AsyncCallLogLevel {
/**
* Log all requests to this instance
* Log all incoming requests
* @defaultValue true
* @privateRemarks
* TODO: rename to called
*/
beCalled?: boolean
/**
* Log all errors produced when responding requests
* Log all errors when responding requests
* @defaultValue true

@@ -67,3 +70,3 @@ */

/**
* Log remote errors
* Log errors from the remote
* @defaultValue true

@@ -73,10 +76,12 @@ */

/**
* Send the call stack to the remote when making requests
* Send the stack to the remote when making requests
* @defaultValue false
* @privateRemarks
* TODO: rename this field to sendRequestStack and move it to AsyncCallOptions.
*/
sendLocalStack?: boolean
/**
* Control if AsyncCall should make the log better
* Style of the log
* @remarks
* If use "pretty", it will call the logger with some CSS to make the log easier to read.
* If this option is set to "pretty", it will log with some CSS to make the log easier to read in the browser devtools.
* Check out this article to know more about it: {@link https://dev.to/annlin/consolelog-with-css-style-1mmp | Console.log with CSS Style}

@@ -87,5 +92,5 @@ * @defaultValue 'pretty'

/**
* Log a function that allows to execute the request with same arguments again
* If log a function that can replay the request
* @remarks
* Do not use this options in the production environment because it will log a closure that captures the arguments of requests. This may cause memory leak.
* Do not use this options in the production environment because it will log a closure that captures all arguments of requests. This may cause memory leak.
* @defaultValue false

@@ -97,10 +102,12 @@ */

/**
* Control the behavior that different from the JSON RPC spec.
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
*/
export interface AsyncCallStrictJSONRPC {
export interface AsyncCallStrictOptions {
/**
* Controls if AsyncCall send an ErrorResponse when the requested method is not defined.
* @remarks
* Set this options to false, AsyncCall will ignore the request (but print a log) if the method is not defined.
* If this option is set to false, AsyncCall will ignore the request and print a log if the method is not defined.
* @defaultValue true

@@ -112,7 +119,27 @@ */

* @remarks
* Set this options to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload. This is useful when the message channel is also used to transfer other kinds of messages.
* If this option is set to false, AsyncCall will ignore the request that cannot be parsed as a valid JSON RPC payload.
* This is useful when the message channel is also used to transfer other kinds of messages.
* @defaultValue true
*/
unknownMessage?: boolean
// TODO: implement this if there is need
/**
* Controls if redundant arguments on the client triggers a warning or error.
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* If this option is set and parameterStructure is "by-name",
* and the client calls with more than 1 argument, it will trigger a warning or error.
*
* @defaultValue false
*/
// redundantArguments?: false | 'error' | 'warning'
}
/**
* Strict options
* @remarks
* Control the behavior that different from the JSON-RPC specification.
* @public
* @deprecated renamed to {@link AsyncCallStrictOptions}
*/
export interface AsyncCallStrictJSONRPC extends AsyncCallStrictOptions {}

@@ -123,29 +150,38 @@ /**

*/
export interface AsyncCallOptions {
export interface AsyncCallOptions<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* This option is used for better log print.
* Name used when pretty log is enabled.
* @defaultValue `rpc`
* @deprecated Renamed to "name".
*/
key?: string
/**
* How to serialize and deserialize the JSON RPC payload
*
* Name used when pretty log is enabled.
* @defaultValue `rpc`
*/
name?: string
/**
* Serializer of the requests and responses.
* @deprecated Use "encoding" option instead. This option will be removed in the next major version.
* @see {@link Serialization}.
*/
serializer?: Serialization
/**
* Encoder of requests and responses.
* @see {@link IsomorphicEncoder} or {@link IsomorphicEncoderFull}.
* @remarks
* See {@link Serialization}.
* There is some built-in serializer:
* There are some built-in encoders:
*
* - {@link NoSerialization} (Not doing anything to the message)
*
* - {@link JSONSerialization} (Using JSON.parse/stringify in the backend)
*
* - {@link https://github.com/jack-works/async-call-rpc#web-deno-and-node-bson | BSONSerialization} (use the {@link https://npmjs.org/bson | bson} as the serializer)
*
* @defaultValue {@link NoSerialization}
* - JSONEncoder: is using JSON.parser and JSON.stringify under the hood.
* @defaultValue undefined
*/
serializer?: Serialization
encoder?:
| IsomorphicEncoder<EncodedRequest, EncodedResponse>
| IsomorphicEncoderFull<EncodedRequest, EncodedResponse>
/**
* Provide the logger of AsyncCall
* @remarks
* See {@link ConsoleInterface}
* Provide the logger
* @see {@link ConsoleInterface}
* @defaultValue globalThis.console
* @privateRemarks
* TODO: allow post-create tweak?
*/

@@ -157,8 +193,16 @@ logger?: ConsoleInterface

* {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/web/websocket.client.ts | Example for CallbackBasedChannel} or {@link https://github.com/Jack-Works/async-call-rpc/blob/main/utils-src/node/websocket.server.ts | Example for EventBasedChannel}
* @privateRemarks
* TODO: split to ClientChannel (onResponse, send) and IsomorphicChannel
*/
channel: CallbackBasedChannel | EventBasedChannel | Promise<CallbackBasedChannel | EventBasedChannel>
channel:
| CallbackBasedChannel<EncodedRequest | EncodedResponse>
| EventBasedChannel<EncodedRequest | EncodedResponse>
| Promise<
| CallbackBasedChannel<EncodedRequest | EncodedResponse>
| EventBasedChannel<EncodedRequest | EncodedResponse>
>
/**
* Choose log level.
* @remarks
* - `true` is a reasonable default value, which means all options are the default options in the {@link AsyncCallLogLevel}
* - `true` is a reasonable default value, which means all options are the default in the {@link AsyncCallLogLevel}
*

@@ -169,6 +213,9 @@ * - `false` is disable all logs

* @defaultValue true
* @privateRemarks
* TODO: allow post-create tweak?
*/
log?: AsyncCallLogLevel | boolean | 'all'
/**
* Control the behavior that different from the JSON RPC spec. See {@link AsyncCallStrictJSONRPC}
* Control the behavior that different from the JSON-RPC spec
* @see {@link AsyncCallStrictJSONRPC}
* @remarks

@@ -182,13 +229,22 @@ * - `true` is to enable all strict options

* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* See {@link https://www.jsonrpc.org/specification#parameter_structures}
* @deprecated renamed to "parameterStructure"
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name'
/**
* Choose flavor of parameter structures defined in the spec
* @see {@link https://www.jsonrpc.org/specification#parameter_structures}
* @remarks
* When using `by-name`, only first parameter is sent to the remote and it must be an object.
*
* When using `by-name`, only first parameter of the requests are sent to the remote and it must be an object.
*
* @privateRemarks
* TODO: review the edge cases when using "by-name".
* TODO: throw an error/print a warning when using "by-name" and the first parameter is not an object/more than 1 parameter is given.
* @defaultValue "by-position"
*/
parameterStructures?: 'by-position' | 'by-name'
parameterStructure?: 'by-position' | 'by-name'
/**

@@ -207,16 +263,18 @@ * Prefer local implementation than remote.

/**
* Control how to report error response according to the exception
* Change the {@link ErrorResponseDetail}.
* @privateRemarks
* TODO: provide a JSONRPCError class to allow customizing ErrorResponseDetail without mapError.
*/
mapError?: ErrorMapFunction<unknown>
/**
* If the instance should be "thenable".
* If the instance is "thenable".
* @defaultValue undefined
* @remarks
* If this options is set to `true`, it will return a `then` method normally (forwards the call to the remote).
* If this options is set to `true`, it will return a `then` method normally (forwards the request to the remote).
*
* If this options is set to `false`, it will return `undefined` even the remote has a method called "then".
* If this options is set to `false`, it will return `undefined`, which means a method named "then" on the remote is not reachable.
*
* If this options is set to `undefined`, it will return `undefined` and show a warning. You must explicitly set this option to `true` or `false` to dismiss the warning.
*
* The motivation of this option is to resolve the problem caused by Promise auto-unwrapping.
* This option is used to resolve the problem caused by Promise auto-unwrapping.
*

@@ -236,14 +294,33 @@ * Consider this code:

thenable?: boolean
/**
* AbortSignal to stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, except for the pending ones.
*/
signal?: AbortSignalLike
/**
* AbortSignal to force stop the instance.
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* `signal` is used to stop the instance. If the `signal` is aborted, then all new requests will be rejected, and the pending requests will be forcibly rejected and pending results will be ignored.
*/
forceSignal?: AbortSignalLike
}
/**
* JSON RPC Request object
* AbortSignal
* @public
* @see {@link https://mdn.io/AbortSignal}
* @remarks
* This is a subset of the AbortSignal interface defined in the [WinterCG](https://wintercg.org/).
*/
export type JSONRPCRequest = {
jsonrpc: '2.0'
id?: string | number | null
method: string
params: readonly unknown[] | object
export interface AbortSignalLike {
readonly aborted: boolean
addEventListener(type: 'abort', listener: () => void, options: { once: boolean }): void
removeEventListener(type: 'abort', listener: () => void): void
throwIfAborted(): void
reason: any
}
/**

@@ -253,6 +330,8 @@ * @public

* @param request - The request object
* @privateRemarks
* TODO: remove T generic parameter
*/
export type ErrorMapFunction<T = unknown> = (
error: unknown,
request: Readonly<JSONRPCRequest>,
request: Readonly<Request>,
) => {

@@ -265,3 +344,3 @@ code: number

/**
* Make all function in the type T becomes async functions and filtering non-Functions out.
* Make all functions in T becomes an async function and filter non-Functions out.
*

@@ -274,8 +353,9 @@ * @remarks

*/
export type AsyncVersionOf<T> = T extends Record<keyof T, (...args: any) => PromiseLike<any>>
? 'then' extends keyof T
? Omit<Readonly<T>, 'then'>
: // in this case we don't want to use Readonly<T>, so it will provide a better experience
T
: _AsyncVersionOf<T>
export type AsyncVersionOf<T> =
T extends Record<keyof T, (...args: any) => PromiseLike<any>>
? 'then' extends keyof T
? Omit<Readonly<T>, 'then'>
: // in this case we don't want to use Readonly<T>, so it will provide a better experience
T
: _AsyncVersionOf<T>
/** @internal */

@@ -289,4 +369,4 @@ export type _AsyncVersionOf<T> = {

: T[key] extends (...args: infer Args) => infer Return // otherwise we convert it to async functions
? (...args: Args) => Promise<Awaited<Return>>
: never
? (...args: Args) => Promise<Awaited<Return>>
: never
}

@@ -310,5 +390,63 @@ /**

/**
* Serialize and deserialize of the JSON RPC payload
* Encoder of the client.
* @public
*/
export interface ClientEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request object.
* @param data - The request object
*/
encodeRequest(data: Requests): EncodedRequest | PromiseLike<EncodedRequest>
/**
* Decode the response object.
* @param encoded - The encoded response object
*/
decodeResponse(encoded: EncodedResponse): Responses | PromiseLike<Responses>
}
/**
* Encoder of the server.
* @public
*/
export interface ServerEncoding<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the response object.
* @param data - The response object
*/
decodeRequest(encoded: EncodedRequest): Requests | PromiseLike<Requests>
/**
* Decode the request object.
* @param encoded - The encoded request object
*/
encodeResponse(data: Responses): EncodedResponse | PromiseLike<EncodedResponse>
}
/**
* Encoder that work for both server and client.
* @public
*/
export interface IsomorphicEncoder<EncodedRequest = unknown, EncodedResponse = unknown> {
/**
* Encode the request or response object.
* @param data - The request or response object
*/
encode(data: Requests | Responses): EncodedRequest | EncodedResponse | PromiseLike<EncodedRequest | EncodedResponse>
/**
* Decode the request or response object.
* @param encoded - The encoded request or response object
*/
decode(encoded: EncodedRequest | EncodedResponse): Requests | Responses | PromiseLike<Requests | Responses>
}
/**
* Encoder that work for both server and client.
* @public
*/
export interface IsomorphicEncoderFull<EncodedRequest = unknown, EncodedResponse = unknown>
extends ClientEncoding<EncodedRequest, EncodedResponse>,
ServerEncoding<EncodedRequest, EncodedResponse>,
Partial<Pick<IsomorphicEncoder, 'decode'>> {}
/**
* Serialize and deserialize of the JSON-RPC payload
* @public
* @deprecated Use {@link IsomorphicEncoder} instead.
*/
export interface Serialization {

@@ -326,1 +464,88 @@ /**

}
//#region JSON-RPC spec types
/**
* A request object or an array of request objects.
* @public
*/
export type Requests = Request | readonly Request[]
/**
* A response object or an array of response objects.
* @public
*/
export type Responses = Response | readonly Response[]
/**
* ID of a JSON-RPC request/response.
* @public
*/
export type ID = string | number | null | undefined
/**
* JSON-RPC Request object.
* @public
* @see https://www.jsonrpc.org/specification#request_object
*/
export interface Request {
readonly jsonrpc: '2.0'
readonly id?: ID
readonly method: string
readonly params: readonly unknown[] | object
/**
* Non-standard field. It records the caller's stack of this Request.
*/
// TODO: Rename to "x-stack" in the next major version.
readonly remoteStack?: string | undefined
}
/**
* JSON-RPC SuccessResponse object.
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export interface SuccessResponse {
readonly jsonrpc: '2.0'
readonly id?: ID
result: unknown
/**
* Non-standard property
* @remarks
* This is a non-standard field that used to represent the result field should be `undefined` instead of `null`.
*
* A field with value `undefined` will be omitted in `JSON.stringify`,
* and if the `"result"` field is omitted, this is no longer a valid JSON-RPC response object.
*
* By default, AsyncCall will convert `undefined` to `null` to keep the response valid, but it _won't_ add this field.
*
* Set `keepUndefined` in JSONEncoderOptions to `"keep"` will add this field.
*
* This field starts with a space, so TypeScript will hide it when providing completion.
*/
// TODO: rename to "x-undefined" or " _u" in the next major version
undef?: unknown
}
/**
* JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export interface ErrorResponse<Error = unknown> {
readonly jsonrpc: '2.0'
readonly id?: ID
readonly error: ErrorResponseDetail<Error>
}
/**
* The "error" record on the JSON-RPC ErrorResponse object.
* @public
* @see https://www.jsonrpc.org/specification#error_object
*/
export interface ErrorResponseDetail<Error = unknown> {
readonly code: number
readonly message: string
readonly data?: Error
}
/**
* A JSON-RPC response object
* @public
* @see https://www.jsonrpc.org/specification#response_object
*/
export type Response = SuccessResponse | ErrorResponse
//#endregion

@@ -7,5 +7,5 @@ export const isString = (x: unknown): x is string => typeof x === 'string'

export const undefined = void 0
export const Object_setPrototypeOf = Object.setPrototypeOf
export const { setPrototypeOf } = Object
export const Promise_resolve = <T>(x: T) => Promise.resolve(x)
export const isArray = Array.isArray
export const replayFunction = () => '() => replay()'
export const { isArray } = Array as { isArray(arg: any): arg is readonly any[] }
export const { apply } = Reflect

@@ -0,3 +1,11 @@

import type { AbortSignalLike } from '../types.js'
class CustomError extends Error {
constructor(public name: string, message: string, public code: number, public stack: string) {
// TODO: support cause
constructor(
public name: string,
message: string,
public code: number,
public stack: string,
) {
super(message)

@@ -42,4 +50,4 @@ }

try {
const E = globalDOMException()
if (type.startsWith(DOMExceptionHeader) && E) {
let E
if (type.startsWith(DOMExceptionHeader) && (E = globalDOMException())) {
const name = type.slice(DOMExceptionHeader.length)

@@ -69,1 +77,4 @@ return new E(message, name)

type DOMException = { new (message: string, name: string): any }
export function onAbort(signal: AbortSignalLike | undefined, callback: () => void) {
signal && signal.addEventListener('abort', callback, { once: true })
}
import { globalDOMException as DOMException, DOMExceptionHeader } from './error.js'
import type { ErrorMapFunction } from '../Async-Call.js'
import { ERROR, isArray, isBoolean, isFunction, isObject, isString, undefined } from './constants.js'
import { ERROR, isArray, isFunction, isObject, undefined } from './constants.js'
import type { Request, SuccessResponse, ErrorResponse, ID, Response } from '../types.js'
export const jsonrpc = '2.0'
export type ID = string | number | null | undefined
/**
* JSONRPC Request object.
*/
export interface Request
extends Readonly<{
jsonrpc: typeof jsonrpc
id?: ID
method: string
params: readonly unknown[] | object
remoteStack?: string
}> {}
export const Request = (id: ID, method: string, params: readonly unknown[] | object, remoteStack?: string): Request => {
export const makeRequest = (id: ID, method: string, params: readonly unknown[] | object, remoteStack?: string): Request => {
const x: Request = { jsonrpc, id, method, params, remoteStack }

@@ -25,13 +13,3 @@ deleteUndefined(x, 'id')

}
/**
* JSONRPC SuccessResponse object.
*/
export interface SuccessResponse
extends Readonly<{
jsonrpc: typeof jsonrpc
id?: ID
result: unknown
}> {}
export const SuccessResponse = (id: ID, result: unknown): SuccessResponse => {
export const makeSuccessResponse = (id: ID, result: unknown): SuccessResponse => {
const x: SuccessResponse = { jsonrpc, id, result }

@@ -41,15 +19,3 @@ deleteUndefined(x, 'id')

}
/**
* JSONRPC ErrorResponse object.
* @public
*/
export interface ErrorResponse<E = unknown>
extends Readonly<{
jsonrpc: typeof jsonrpc
id?: ID
error: Readonly<{ code: number; message: string; data?: E }>
}> {}
export const ErrorResponse = <T>(id: ID, code: number, message: string, data?: T): ErrorResponse<T> => {
export const makeErrorResponse = <T>(id: ID, code: number, message: string, data?: T): ErrorResponse<T> => {
if (id === undefined) id = null

@@ -75,4 +41,4 @@ code = Math.floor(code)

// InternalError -32603 'Internal error'
export const ErrorResponseInvalidRequest = (id: ID) => ErrorResponse(id, -32600, 'Invalid Request')
export const ErrorResponseMethodNotFound = (id: ID) => ErrorResponse(id, -32601, 'Method not found')
export const ErrorResponseInvalidRequest = (id: ID) => makeErrorResponse(id, -32600, 'Invalid Request')
export const ErrorResponseMethodNotFound = (id: ID) => makeErrorResponse(id, -32601, 'Method not found')

@@ -86,3 +52,3 @@ type AsyncCallErrorDetail = {

const { code, message, data } = mapper(e, request)
return ErrorResponse(id, code, message, data)
return makeErrorResponse(id, code, message, data)
}

@@ -97,3 +63,4 @@

if (E && e instanceof E) type = DOMExceptionHeader + e.name
if (isString(e) || typeof e === 'number' || isBoolean(e) || typeof e === 'bigint') {
const eType = typeof e
if (eType == 'string' || eType === 'number' || eType == 'boolean' || eType == 'bigint') {
type = ERROR

@@ -106,6 +73,2 @@ message = String(e)

/**
* A JSONRPC response object
*/
export type Response = SuccessResponse | ErrorResponse

@@ -123,4 +86,2 @@ export const isJSONRPCObject = (data: any): data is Response | Request => {

export { isObject } from './constants.js'
const toString = (_default: string, val: () => any) => {

@@ -127,0 +88,0 @@ try {

@@ -1,5 +0,2 @@

//#region Serialization
import { isObject } from './jsonrpc.js'
import { undefined } from './constants.js'
import { isObject, undefined } from './constants.js'
import type { Serialization } from '../types.js'

@@ -70,2 +67,1 @@

})
//#endregion
import type { serialize as S, deserialize as D } from 'bson'
import type { Serialization } from 'async-call-rpc'
import type { Serialization } from 'async-call-rpc' with { 'resolution-mode': 'import' }
/**
* @deprecated This will be removed in the next major version.
*/
export const BSON_Serialization = (

@@ -5,0 +8,0 @@ {

import type { encode as S, decode as D } from '@msgpack/msgpack'
import type { Serialization } from 'async-call-rpc'
import type { Serialization } from 'async-call-rpc' with { 'resolution-mode': 'import' }
/**
* @deprecated This will be removed in the next major version.
*/
export const Msgpack_Serialization = (

@@ -4,0 +8,0 @@ {

import type { Server } from 'ws'
import type WebSocket from 'ws'
import type { CallbackBasedChannel } from 'async-call-rpc'
import type { CallbackBasedChannel } from 'async-call-rpc' with { 'resolution-mode': 'import' }
type JSONRPCHandlerCallback = (data: unknown) => Promise<unknown>

@@ -5,0 +6,0 @@ export class WebSocketChannel implements CallbackBasedChannel {

import type { Serialization } from 'async-call-rpc'
import type { serialize as S, deserialize as D } from 'bson'
/**
* @deprecated This will be removed in the next major version.
*/
export const BSON_Serialization = ({

@@ -4,0 +8,0 @@ deserialize,

import type { encode as S, decode as D } from '@msgpack/msgpack'
import type { Serialization } from 'async-call-rpc'
/**
* @deprecated This will be removed in the next major version.
*/
export const Msgpack_Serialization = ({ encode, decode }: { encode: typeof S; decode: typeof D }): Serialization => ({

@@ -4,0 +8,0 @@ async deserialization(data: any) {

import type { serialize as S, deserialize as D } from 'bson';
import type { Serialization } from 'async-call-rpc';
import type { Serialization } from 'async-call-rpc' with { 'resolution-mode': 'import' };
/**
* @deprecated This will be removed in the next major version.
*/
export declare const BSON_Serialization: ({ deserialize, serialize, }?: {

@@ -4,0 +7,0 @@ serialize: typeof S;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BSON_Serialization = void 0;
/**
* @deprecated This will be removed in the next major version.
*/
const BSON_Serialization = ({ deserialize, serialize, } = require('bson')) => ({

@@ -5,0 +8,0 @@ deserialization(data) {

import type { encode as S, decode as D } from '@msgpack/msgpack';
import type { Serialization } from 'async-call-rpc';
import type { Serialization } from 'async-call-rpc' with { 'resolution-mode': 'import' };
/**
* @deprecated This will be removed in the next major version.
*/
export declare const Msgpack_Serialization: ({ encode, decode, }?: {

@@ -4,0 +7,0 @@ encode: typeof S;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Msgpack_Serialization = void 0;
/**
* @deprecated This will be removed in the next major version.
*/
const Msgpack_Serialization = ({ encode, decode, } = require('@msgpack/msgpack')) => ({

@@ -5,0 +8,0 @@ deserialization(data) {

@@ -0,4 +1,5 @@

/// <reference types="node" />
import type { Server } from 'ws';
import type WebSocket from 'ws';
import type { CallbackBasedChannel } from 'async-call-rpc';
import type { CallbackBasedChannel } from 'async-call-rpc' with { 'resolution-mode': 'import' };
type JSONRPCHandlerCallback = (data: unknown) => Promise<unknown>;

@@ -8,5 +9,5 @@ export declare class WebSocketChannel implements CallbackBasedChannel {

constructor(server: Server);
setup(callback: JSONRPCHandlerCallback): () => Server<WebSocket.WebSocket>;
setup(callback: JSONRPCHandlerCallback): () => Server<typeof WebSocket, typeof import("http").IncomingMessage>;
}
export {};
//# sourceMappingURL=websocket.server.d.ts.map
import type { Serialization } from 'async-call-rpc';
import type { serialize as S, deserialize as D } from 'bson';
/**
* @deprecated This will be removed in the next major version.
*/
export declare const BSON_Serialization: ({ deserialize, serialize, }: {

@@ -4,0 +7,0 @@ serialize: typeof S;

@@ -0,1 +1,4 @@

/**
* @deprecated This will be removed in the next major version.
*/
export const BSON_Serialization = ({ deserialize, serialize, }) => ({

@@ -2,0 +5,0 @@ async deserialization(data) {

import type { encode as S, decode as D } from '@msgpack/msgpack';
import type { Serialization } from 'async-call-rpc';
/**
* @deprecated This will be removed in the next major version.
*/
export declare const Msgpack_Serialization: ({ encode, decode }: {

@@ -4,0 +7,0 @@ encode: typeof S;

@@ -0,1 +1,4 @@

/**
* @deprecated This will be removed in the next major version.
*/
export const Msgpack_Serialization = ({ encode, decode }) => ({

@@ -2,0 +5,0 @@ async deserialization(data) {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc