1 | /**
|
2 | * ! This file MUST NOT contain any import statement.
|
3 | * ! This file is part of public API of this package (for Deno users).
|
4 | */
|
5 | /**
|
6 | * This interface represents a "on message" - "send response" model.
|
7 | * @remarks
|
8 | * Usually used for there is only 1 remote (act like a client).
|
9 | * Example: {@link https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/websocket.server.ts | Example for EventBasedChannel}
|
10 | * @public
|
11 | */
|
12 | interface EventBasedChannel<Data = unknown> {
|
13 | /**
|
14 | * Register the message listener.
|
15 | * @param listener - The message listener.
|
16 | * @returns a function that unregister the listener.
|
17 | */
|
18 | on(listener: (data: Data) => void): void | (() => void);
|
19 | /**
|
20 | * Send the data to the remote side.
|
21 | * @param data - The data should send to the remote side.
|
22 | */
|
23 | send(data: Data): void;
|
24 | }
|
25 | /**
|
26 | * This interface represents a "callback" model.
|
27 | * @remarks
|
28 | * Usually used for there are many remotes (act like a server).
|
29 | * Example: {@link https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/websocket.client.ts | Example for CallbackBasedChannel}
|
30 | * @public
|
31 | */
|
32 | interface CallbackBasedChannel<Data = unknown> extends Partial<EventBasedChannel<Data>> {
|
33 | /**
|
34 | * Setup the CallbackBasedChannel.
|
35 | * @param jsonRPCHandlerCallback - A function that will execute the JSON RPC request then give the result back. If the result is undefined, it means no response is created.
|
36 | * @param isValidPayload - A util function that will try to validate if the message is a valid JSON RPC request. It will be synchronous if possible.
|
37 | * @returns a function that unregister the setup.
|
38 | */
|
39 | setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown) => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown) => boolean | Promise<boolean>): (() => void) | void;
|
40 | }
|
41 | /**
|
42 | * Log options of AsyncCall
|
43 | * @remarks
|
44 | * This option controls how AsyncCall should log RPC calls to the console.
|
45 | * @public
|
46 | */
|
47 | interface AsyncCallLogLevel {
|
48 | /**
|
49 | * Log all requests to this instance
|
50 | * @defaultValue true
|
51 | */
|
52 | beCalled?: boolean;
|
53 | /**
|
54 | * Log all errors produced when responding requests
|
55 | * @defaultValue true
|
56 | */
|
57 | localError?: boolean;
|
58 | /**
|
59 | * Log remote errors
|
60 | * @defaultValue true
|
61 | */
|
62 | remoteError?: boolean;
|
63 | /**
|
64 | * Send the call stack to the remote when making requests
|
65 | * @defaultValue false
|
66 | */
|
67 | sendLocalStack?: boolean;
|
68 | /**
|
69 | * Control if AsyncCall should make the log better
|
70 | * @remarks
|
71 | * If use "pretty", it will call the logger with some CSS to make the log easier to read.
|
72 | * Check out this article to know more about it: {@link https://dev.to/annlin/consolelog-with-css-style-1mmp | Console.log with CSS Style}
|
73 | * @defaultValue 'pretty'
|
74 | */
|
75 | type?: 'basic' | 'pretty';
|
76 | /**
|
77 | * Log a function that allows to execute the request with same arguments again
|
78 | * @remarks
|
79 | * 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.
|
80 | * @defaultValue false
|
81 | */
|
82 | requestReplay?: boolean;
|
83 | }
|
84 | /**
|
85 | * Control the behavior that different from the JSON RPC spec.
|
86 | * @public
|
87 | */
|
88 | interface AsyncCallStrictJSONRPC {
|
89 | /**
|
90 | * Controls if AsyncCall send an ErrorResponse when the requested method is not defined.
|
91 | * @remarks
|
92 | * Set this options to false, AsyncCall will ignore the request (but print a log) if the method is not defined.
|
93 | * @defaultValue true
|
94 | */
|
95 | methodNotFound?: boolean;
|
96 | /**
|
97 | * Controls if AsyncCall send an ErrorResponse when the message is not valid.
|
98 | * @remarks
|
99 | * 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.
|
100 | * @defaultValue true
|
101 | */
|
102 | unknownMessage?: boolean;
|
103 | }
|
104 | /**
|
105 | * Options for {@link AsyncCall}
|
106 | * @public
|
107 | */
|
108 | interface AsyncCallOptions {
|
109 | /**
|
110 | * This option is used for better log print.
|
111 | * @defaultValue `rpc`
|
112 | */
|
113 | key?: string;
|
114 | /**
|
115 | * How to serialize and deserialize the JSON RPC payload
|
116 | *
|
117 | * @remarks
|
118 | * See {@link Serialization}.
|
119 | * There is some built-in serializer:
|
120 | *
|
121 | * - {@link NoSerialization} (Not doing anything to the message)
|
122 | *
|
123 | * - {@link JSONSerialization} (Using JSON.parse/stringify in the backend)
|
124 | *
|
125 | * - {@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)
|
126 | *
|
127 | * @defaultValue {@link NoSerialization}
|
128 | */
|
129 | serializer?: Serialization;
|
130 | /**
|
131 | * Provide the logger of AsyncCall
|
132 | * @remarks
|
133 | * See {@link ConsoleInterface}
|
134 | * @defaultValue globalThis.console
|
135 | */
|
136 | logger?: ConsoleInterface;
|
137 | /**
|
138 | * The message channel to exchange messages between server and client
|
139 | * @example
|
140 | * {@link https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/websocket.client.ts | Example for CallbackBasedChannel} or {@link https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/websocket.server.ts | Example for EventBasedChannel}
|
141 | */
|
142 | channel: CallbackBasedChannel | EventBasedChannel;
|
143 | /**
|
144 | * Choose log level.
|
145 | * @remarks
|
146 | * - `true` is a reasonable default value, which means all options are the default options in the {@link AsyncCallLogLevel}
|
147 | *
|
148 | * - `false` is disable all logs
|
149 | *
|
150 | * - `"all"` is enable all logs (stronger than `true`).
|
151 | * @defaultValue true
|
152 | */
|
153 | log?: AsyncCallLogLevel | boolean | 'all';
|
154 | /**
|
155 | * Control the behavior that different from the JSON RPC spec. See {@link AsyncCallStrictJSONRPC}
|
156 | * @remarks
|
157 | * - `true` is to enable all strict options
|
158 | * - `false` is to disable all strict options
|
159 | * @defaultValue true
|
160 | */
|
161 | strict?: AsyncCallStrictJSONRPC | boolean;
|
162 | /**
|
163 | * Choose flavor of parameter structures defined in the spec
|
164 | * @remarks
|
165 | *
|
166 | * See {@link https://www.jsonrpc.org/specification#parameter_structures}
|
167 | *
|
168 | * When using `by-name`, only first parameter of the requests are sent to the remote and it must be an object.
|
169 | *
|
170 | * @privateRemarks
|
171 | * TODO: review the edge cases when using "by-name".
|
172 | * @defaultValue "by-position"
|
173 | */
|
174 | parameterStructures?: 'by-position' | 'by-name';
|
175 | /**
|
176 | * Prefer local implementation than remote.
|
177 | * @remarks
|
178 | * If you call a RPC method and it is also defined in the local, open this flag will call the local implementation directly instead of send a RPC request. No logs / serialization will be performed if a local implementation is used.
|
179 | * @defaultValue false
|
180 | */
|
181 | preferLocalImplementation?: boolean;
|
182 | /**
|
183 | * The ID generator of each JSON RPC request
|
184 | * @defaultValue () =\> Math.random().toString(36).slice(2)
|
185 | */
|
186 | idGenerator?(): string | number;
|
187 | /**
|
188 | * Control how to report error response according to the exception
|
189 | */
|
190 | mapError?: ErrorMapFunction<unknown>;
|
191 | /**
|
192 | * If the instance should be "thenable".
|
193 | * @defaultValue undefined
|
194 | * @remarks
|
195 | * If this options is set to `true`, it will return a `then` method normally (forwards the call to the remote).
|
196 | *
|
197 | * If this options is set to `false`, it will return `undefined` even the remote has a method called "then".
|
198 | *
|
199 | * 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.
|
200 | *
|
201 | * The motivation of this option is to resolve the problem caused by Promise auto-unwrapping.
|
202 | *
|
203 | * Consider this code:
|
204 | *
|
205 | * ```ts
|
206 | * async function getRPC() {
|
207 | * return AsyncCall(...)
|
208 | * }
|
209 | * ```
|
210 | *
|
211 | * According to the JS semantics, it will invoke the "then" method immediately on the returning instance which is unwanted in most scenarios.
|
212 | *
|
213 | * To avoid this problem, methods called "then" are omitted from the type signatures. Strongly suggest to not use "then" as your RPC method name.
|
214 | */
|
215 | thenable?: boolean;
|
216 | }
|
217 | /**
|
218 | * JSON RPC Request object
|
219 | * @public
|
220 | */
|
221 | declare type JSONRPCRequest = {
|
222 | jsonrpc: '2.0';
|
223 | id?: string | number | null;
|
224 | method: string;
|
225 | params: readonly unknown[] | object;
|
226 | };
|
227 | /**
|
228 | * @public
|
229 | * @param error - The exception
|
230 | * @param request - The request object
|
231 | */
|
232 | declare type ErrorMapFunction<T = unknown> = (error: unknown, request: Readonly<JSONRPCRequest>) => {
|
233 | code: number;
|
234 | message: string;
|
235 | data?: T;
|
236 | };
|
237 | /**
|
238 | * Make all function in the type T becomes async functions and filtering non-Functions out.
|
239 | *
|
240 | * @remarks
|
241 | * Only generics signatures on function that returning an Promise<T> will be preserved due to the limitation of TypeScript.
|
242 | *
|
243 | * Method called `then` are intentionally removed because it is very likely to be a foot gun in promise auto-unwrap.
|
244 | * @internal
|
245 | */
|
246 | declare type _AsyncVersionOf<T> = {
|
247 | readonly [key in keyof T as key extends 'then' ? never : T[key] extends Function ? key : never]: T[key] extends (...args: any) => Promise<any> ? T[key] : T[key] extends (...args: infer Args) => infer Return ? (...args: Args) => Promise<Return extends PromiseLike<infer U> ? U : Return> : never;
|
248 | };
|
249 | /**
|
250 | * The minimal Console interface that AsyncCall needs.
|
251 | * @public
|
252 | * @remarks
|
253 | * The method not provided will use "log" as it's fallback.
|
254 | */
|
255 | interface ConsoleInterface {
|
256 | warn?(...args: unknown[]): void;
|
257 | debug?(...args: unknown[]): void;
|
258 | log(...args: unknown[]): void;
|
259 | groupCollapsed?(...args: unknown[]): void;
|
260 | groupEnd?(...args: unknown[]): void;
|
261 | error?(...args: unknown[]): void;
|
262 | }
|
263 |
|
264 | /**
|
265 | * Serialize and deserialize of the JSON RPC payload
|
266 | * @public
|
267 | */
|
268 | interface Serialization {
|
269 | /**
|
270 | * Serialize data
|
271 | * @param from - original data
|
272 | */
|
273 | serialization(from: any): unknown | PromiseLike<unknown>;
|
274 | /**
|
275 | * Deserialize data
|
276 | * @param serialized - Serialized data
|
277 | */
|
278 | deserialization(serialized: unknown): unknown | PromiseLike<unknown>;
|
279 | }
|
280 |
|
281 | /**
|
282 | * Make the returning type to `Promise<void>`
|
283 | * @internal
|
284 | * @remarks
|
285 | * Due to the limitation of TypeScript, generic signatures cannot be preserved
|
286 | * if the function is the top level parameter of this utility type,
|
287 | * or the function is not returning `Promise<void>`.
|
288 | */
|
289 | declare type _IgnoreResponse<T> = T extends (...args: infer Args) => unknown ? (...args: Args) => Promise<void> : {
|
290 | [key in keyof T as T[key] extends Function ? key : never]: T[key] extends (...args: infer Args) => infer Return ? Return extends Promise<void> ? T[key] : (...args: Args) => Promise<void> : never;
|
291 | };
|
292 | /**
|
293 | * Wrap the AsyncCall instance to send notification.
|
294 | * @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
|
295 | * @example
|
296 | * const notifyOnly = notify(AsyncCall(...))
|
297 | * @public
|
298 | */
|
299 | declare function notify<T extends object>(instanceOrFnOnInstance: T): _IgnoreResponse<T>;
|
300 |
|
301 | /**
|
302 | * Serialization implementation that do nothing
|
303 | * @remarks {@link Serialization}
|
304 | * @public
|
305 | */
|
306 | declare const NoSerialization: Serialization;
|
307 | /**
|
308 | * Create a serialization by JSON.parse/stringify
|
309 | *
|
310 | * @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
|
311 | * @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
|
312 | * @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
|
313 | *
|
314 | * If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
|
315 | *
|
316 | * Options:
|
317 | * - `"null"`(**default**): convert it to null.
|
318 | * - `"keep"`: try to keep it by additional property "undef".
|
319 | * - `false`: Don't keep it, let it break.
|
320 | * @remarks {@link Serialization}
|
321 | * @public
|
322 | */
|
323 | declare const JSONSerialization: (replacerAndReceiver?: [(((key: string, value: any) => any) | undefined)?, (((key: string, value: any) => any) | undefined)?], space?: string | number | undefined, undefinedKeepingBehavior?: 'keep' | 'null' | false) => Serialization;
|
324 |
|
325 | /**
|
326 | * Wrap the AsyncCall instance to use batch call.
|
327 | * @param asyncCallInstance - The AsyncCall instance
|
328 | * @example
|
329 | * const [batched, send, drop] = batch(AsyncCall(...))
|
330 | * batched.call1() // pending
|
331 | * batched.call2() // pending
|
332 | * send() // send all pending requests
|
333 | * drop() // drop all pending requests
|
334 | * @returns It will return a tuple.
|
335 | *
|
336 | * The first item is the batched version of AsyncCall instance passed in.
|
337 | *
|
338 | * The second item is a function to send all pending requests.
|
339 | *
|
340 | * The third item is a function to drop and reject all pending requests.
|
341 | * @public
|
342 | */
|
343 | declare function batch<T extends object>(asyncCallInstance: T): [T, () => void, (error?: unknown) => void];
|
344 |
|
345 | /**
|
346 | * Create a RPC server & client.
|
347 | *
|
348 | * @remarks
|
349 | * See {@link AsyncCallOptions}
|
350 | *
|
351 | * thisSideImplementation can be a Promise so you can write:
|
352 | *
|
353 | * ```ts
|
354 | * export const service = AsyncCall(typeof window === 'object' ? {} : import('./backend/service.js'), {})
|
355 | * ```
|
356 | *
|
357 | * @param thisSideImplementation - The implementation when this AsyncCall acts as a JSON RPC server. Can be a Promise.
|
358 | * @param options - {@link AsyncCallOptions}
|
359 | * @typeParam OtherSideImplementedFunctions - The type of the API that server expose. For any function on this interface, it will be converted to the async version.
|
360 | * @returns Same as the `OtherSideImplementedFunctions` type parameter, but every function in that interface becomes async and non-function value is removed. Method called "then" are also removed.
|
361 | * @public
|
362 | */
|
363 | declare function AsyncCall<OtherSideImplementedFunctions = {}>(thisSideImplementation: null | undefined | object | Promise<object>, options: AsyncCallOptions): _AsyncVersionOf<OtherSideImplementedFunctions>;
|
364 |
|
365 | export { AsyncCall, AsyncCallLogLevel, AsyncCallOptions, AsyncCallStrictJSONRPC, CallbackBasedChannel, ConsoleInterface as Console, ConsoleInterface, ErrorMapFunction, EventBasedChannel, JSONRPCRequest, JSONSerialization, NoSerialization, Serialization, _AsyncVersionOf, _IgnoreResponse, batch, notify };
|
366 |
|
\ | No newline at end of file |