UNPKG

34.5 kBJavaScriptView Raw
1/// <reference types="./full.d.ts" />
2(function (global, factory) {
3 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4 typeof define === 'function' && define.amd ? define(['exports'], factory) :
5 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.AsyncCall = {}));
6}(this, (function (exports) { 'use strict';
7
8 class CustomError extends Error {
9 constructor( name, message, code, stack) {
10 super(message);this.name = name;this.code = code;this.stack = stack;
11 }
12 }
13 const Err_Cannot_find_a_running_iterator_with_given_ID = {};
14 const Err_Only_string_can_be_the_RPC_method_name = {};
15 const Err_Cannot_call_method_starts_with_rpc_dot_directly = {};
16 const Err_Then_is_accessed_on_local_implementation_Please_explicitly_mark_if_it_is_thenable_in_the_options = {};
17 const Messages = [
18 Err_Cannot_find_a_running_iterator_with_given_ID,
19 Err_Only_string_can_be_the_RPC_method_name,
20 Err_Cannot_call_method_starts_with_rpc_dot_directly,
21 Err_Then_is_accessed_on_local_implementation_Please_explicitly_mark_if_it_is_thenable_in_the_options,
22 ];
23 // https://github.com/Jack-Works/async-call-rpc/wiki/Error-messages
24 function makeHostedMessage(id, error) {
25 const n = Messages.indexOf(id);
26 error.message += `Error ${n}: https://github.com/Jack-Works/async-call-rpc/wiki/Errors#` + n;
27 return error
28 }
29 // ! side effect
30 /** These Error is defined in ECMAScript spec */
31 const errors = {
32 // @ts-ignore
33 __proto__: null,
34 Error,
35 EvalError,
36 RangeError,
37 ReferenceError,
38 SyntaxError,
39 TypeError,
40 URIError,
41 };
42 const DOMExceptionHeader = 'DOMException:';
43 /**
44 * AsyncCall support somehow transfer ECMAScript Error
45 */
46 const RecoverError = (type, message, code, stack) => {
47 try {
48 const E = globalDOMException();
49 if (type.startsWith(DOMExceptionHeader) && E) {
50 const name = type.slice(DOMExceptionHeader.length);
51 return new E(message, name)
52 } else if (type in errors) {
53 const e = new errors[type](message);
54 e.stack = stack;
55 // @ts-ignore
56 e.code = code;
57 return e
58 } else {
59 return new CustomError(type, message, code, stack)
60 }
61 } catch (e2) {
62 return new Error(`E${code} ${type}: ${message}\n${stack}`)
63 }
64 };
65 const removeStackHeader = (stack) => String(stack).replace(/^.+\n.+\n/, '');
66 // ! side effect
67 const globalDOMException = (() => {
68 try {
69 // @ts-ignore
70 return DOMException
71 } catch (e3) {}
72 });
73
74 const isString = (x) => typeof x === 'string';
75 const isBoolean = (x) => typeof x === 'boolean';
76 const isFunction = (x) => typeof x === 'function';
77 const isObject = (params) => typeof params === 'object' && params !== null;
78 const ERROR = 'Error';
79 const undefined$1 = void 0;
80 const Object_setPrototypeOf = Object.setPrototypeOf;
81 const Promise_reject = (x) => Promise.reject(x);
82 const Promise_resolve = (x) => Promise.resolve(x);
83 const isArray = Array.isArray;
84 const replayFunction = () => '() => replay()';
85
86 const jsonrpc = '2.0';
87
88
89
90
91
92
93
94
95
96
97
98
99
100 const Request = (id, method, params, remoteStack) => {
101 const x = { jsonrpc, id, method, params, remoteStack };
102 deleteUndefined(x, 'id');
103 deleteFalsy(x, 'remoteStack');
104 return x
105 };
106
107 /**
108 * JSONRPC SuccessResponse object.
109 */
110
111
112
113
114
115
116 const SuccessResponse = (id, result) => {
117 const x = { jsonrpc, id, result };
118 deleteUndefined(x, 'id');
119 return x
120 };
121
122 /**
123 * JSONRPC ErrorResponse object.
124 * @public
125 */
126
127
128
129
130
131
132
133 const ErrorResponse = (id, code, message, data) => {
134 if (id === undefined$1) id = null;
135 code = Math.floor(code);
136 if (Number.isNaN(code)) code = -1;
137 const x = { jsonrpc, id, error: { code, message, data } };
138 deleteUndefined(x.error, 'data');
139 return x
140 };
141 // Pre defined error in section 5.1
142 // ! side effect
143 const ErrorResponseParseError = (e, mapper) => {
144 const obj = ErrorResponseMapped({} , e, mapper);
145 const o = obj.error;
146 o.code = -32700;
147 o.message = 'Parse error';
148 return obj
149 };
150
151 // Not using.
152 // InvalidParams -32602 'Invalid params'
153 // InternalError -32603 'Internal error'
154 const ErrorResponseInvalidRequest = (id) => ErrorResponse(id, -32600, 'Invalid Request');
155 const ErrorResponseMethodNotFound = (id) => ErrorResponse(id, -32601, 'Method not found');
156
157
158
159
160
161 const ErrorResponseMapped = (request, e, mapper) => {
162 const { id } = request;
163 const { code, message, data } = mapper(e, request);
164 return ErrorResponse(id, code, message, data)
165 };
166
167 const defaultErrorMapper = (stack = '', code = -1) => (e) => {
168 let message = toString('', () => (e ).message);
169 let type = toString(ERROR, (ctor = (e ).constructor) => isFunction(ctor) && ctor.name);
170 const E = globalDOMException();
171 if (E && e instanceof E) type = DOMExceptionHeader + e.name;
172 if (isString(e) || typeof e === 'number' || isBoolean(e) || typeof e === 'bigint') {
173 type = ERROR;
174 message = String(e);
175 }
176 const data = stack ? { stack, type } : { type };
177 return { code, message, data }
178 };
179
180 /**
181 * A JSONRPC response object
182 */
183
184
185 const isJSONRPCObject = (data) => {
186 if (!isObject(data)) return false
187 if (!hasKey(data, 'jsonrpc')) return false
188 if (data.jsonrpc !== jsonrpc) return false
189 if (hasKey(data, 'params')) {
190 const params = (data ).params;
191 if (!isArray(params) && !isObject(params)) return false
192 }
193 return true
194 };
195
196 const hasKey = (
197 obj,
198 key,
199
200
201
202 ) => key in obj;
203
204 const toString = (_default, val) => {
205 try {
206 const v = val();
207 if (v === undefined$1) return _default
208 return String(v)
209 } catch (e2) {
210 return _default
211 }
212 };
213 const deleteUndefined = (x, key) => {
214 if (x[key] === undefined$1) delete x[key];
215 };
216 const deleteFalsy = (x, key) => {
217 if (!x[key]) delete x[key];
218 };
219
220 //#region Serialization
221
222
223
224 /**
225 * Serialization implementation that do nothing
226 * @remarks {@link Serialization}
227 * @public
228 */
229 const NoSerialization = {
230 serialization(from) {
231 return from
232 },
233 deserialization(serialized) {
234 return serialized
235 },
236 };
237
238 /**
239 * Create a serialization by JSON.parse/stringify
240 *
241 * @param replacerAndReceiver - Replacer and receiver of JSON.parse/stringify
242 * @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
243 * @param undefinedKeepingBehavior - How to keep "undefined" in result of SuccessResponse?
244 *
245 * If it is not handled properly, JSON.stringify will emit an invalid JSON RPC object.
246 *
247 * Options:
248 * - `"null"`(**default**): convert it to null.
249 * - `"keep"`: try to keep it by additional property "undef".
250 * - `false`: Don't keep it, let it break.
251 * @remarks {@link Serialization}
252 * @public
253 */
254 const JSONSerialization = (
255 replacerAndReceiver = [
256 undefined$1,
257 undefined$1,
258 ],
259 space,
260 undefinedKeepingBehavior = 'null',
261 ) => ({
262 serialization(from) {
263 if (undefinedKeepingBehavior && isObject(from) && hasKey(from, 'result') && from.result === undefined$1) {
264 const alt = { ...from };
265 alt.result = null;
266 if (undefinedKeepingBehavior === 'keep') (alt ).undef = true;
267 from = alt;
268 }
269 return JSON.stringify(from, replacerAndReceiver[0], space)
270 },
271 deserialization(serialized) {
272 const result = JSON.parse(serialized , replacerAndReceiver[1]);
273 if (
274 isObject(result) &&
275 hasKey(result, 'result') &&
276 result.result === null &&
277 hasKey(result, 'undef') &&
278 result.undef === true
279 ) {
280 result.result = undefined$1;
281 delete result.undef;
282 }
283 return result
284 },
285 });
286 //#endregion
287
288 const i$1 = 'AsyncCall/';
289 // ! side effect
290 const AsyncCallIgnoreResponse = Symbol.for(i$1 + 'ignored');
291 const AsyncCallNotify = Symbol.for(i$1 + 'notify');
292 const AsyncCallBatch = Symbol.for(i$1 + 'batch');
293
294 /**
295 * Make the returning type to `Promise<void>`
296 * @internal
297 * @remarks
298 * Due to the limitation of TypeScript, generic signatures cannot be preserved
299 * if the function is the top level parameter of this utility type,
300 * or the function is not returning `Promise<void>`.
301 */
302
303
304
305
306
307
308
309
310
311
312
313 /**
314 * Wrap the AsyncCall instance to send notification.
315 * @param instanceOrFnOnInstance - The AsyncCall instance or function on the AsyncCall instance
316 * @example
317 * const notifyOnly = notify(AsyncCall(...))
318 * @public
319 */
320
321 function notify(instanceOrFnOnInstance) {
322 if (isFunction(instanceOrFnOnInstance)) return (instanceOrFnOnInstance )[AsyncCallNotify]
323 return new Proxy(instanceOrFnOnInstance, { get: notifyTrap })
324 }
325 const notifyTrap = (target, p) => {
326 return target[p][AsyncCallNotify]
327 };
328
329 /**
330 * Wrap the AsyncCall instance to use batch call.
331 * @param asyncCallInstance - The AsyncCall instance
332 * @example
333 * const [batched, send, drop] = batch(AsyncCall(...))
334 * batched.call1() // pending
335 * batched.call2() // pending
336 * send() // send all pending requests
337 * drop() // drop all pending requests
338 * @returns It will return a tuple.
339 *
340 * The first item is the batched version of AsyncCall instance passed in.
341 *
342 * The second item is a function to send all pending requests.
343 *
344 * The third item is a function to drop and reject all pending requests.
345 * @public
346 */
347 function batch(asyncCallInstance) {
348 let queue = [];
349 return [
350 new Proxy({ __proto__: null } , {
351 get(cache, p) {
352 if (isString(p) && cache[p]) return cache[p]
353 // @ts-ignore
354 const f = (...args) => asyncCallInstance[AsyncCallBatch](queue, p, ...args);
355 // @ts-ignore
356 f[AsyncCallNotify] = (...args) =>
357 // @ts-ignore
358 asyncCallInstance[AsyncCallBatch][AsyncCallNotify](queue, p, ...args);
359 // @ts-ignore
360 f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
361 isString(p) && Object.defineProperty(cache, p, { value: f, configurable: true });
362 return f
363 },
364 }),
365 (r = queue.r) => r && r[0](),
366 (error = new Error('Aborted'), r = queue.r) => {
367 r && r[1](error);
368 queue = [];
369 },
370 ]
371 }
372
373 const generateRandomID = () => Math.random().toString(36).slice(2);
374
375 const undefinedToTrue = (x) => (x === void 0 ? true : x);
376
377
378
379
380
381
382
383
384
385 const normalizeLogOptions = (log) => {
386 if (log === 'all') return [true, true, true, true, true, true]
387 if (!isBoolean(log)) {
388 const { beCalled, localError, remoteError, type, requestReplay, sendLocalStack } = log;
389 return [
390 undefinedToTrue(beCalled),
391 undefinedToTrue(localError),
392 undefinedToTrue(remoteError),
393 type !== 'basic',
394 requestReplay,
395 sendLocalStack,
396 ]
397 }
398 if (log) return [true, true, true, true]
399 return []
400 };
401
402 const normalizeStrictOptions = (strict) => {
403 if (!isBoolean(strict)) {
404 const { methodNotFound, unknownMessage } = strict;
405 return [methodNotFound, unknownMessage]
406 }
407 return [strict, strict]
408 };
409
410 /**
411 * Create a RPC server & client.
412 *
413 * @remarks
414 * See {@link AsyncCallOptions}
415 *
416 * thisSideImplementation can be a Promise so you can write:
417 *
418 * ```ts
419 * export const service = AsyncCall(typeof window === 'object' ? {} : import('./backend/service.js'), {})
420 * ```
421 *
422 * @param thisSideImplementation - The implementation when this AsyncCall acts as a JSON RPC server. Can be a Promise.
423 * @param options - {@link AsyncCallOptions}
424 * @typeParam OtherSideImplementedFunctions - The type of the API that server expose. For any function on this interface, it will be converted to the async version.
425 * @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.
426 * @public
427 */
428 function AsyncCall(
429 thisSideImplementation,
430 options,
431 ) {
432 let isThisSideImplementationPending = true;
433 let resolvedThisSideImplementationValue = undefined$1;
434 let rejectedThisSideImplementation = undefined$1;
435 // This promise should never fail
436 const awaitThisSideImplementation = async () => {
437 try {
438 resolvedThisSideImplementationValue = await thisSideImplementation;
439 } catch (e) {
440 rejectedThisSideImplementation = e;
441 console_error('AsyncCall failed to start', e);
442 } finally {
443 isThisSideImplementationPending = false;
444 }
445 };
446
447 const {
448 serializer = NoSerialization,
449 key: logKey = 'rpc',
450 strict = true,
451 log = true,
452 parameterStructures = 'by-position',
453 preferLocalImplementation = false,
454 idGenerator = generateRandomID,
455 mapError,
456 logger,
457 channel,
458 thenable,
459 } = options;
460
461 if (thisSideImplementation instanceof Promise) awaitThisSideImplementation();
462 else {
463 resolvedThisSideImplementationValue = thisSideImplementation;
464 isThisSideImplementationPending = false;
465 }
466
467 const [banMethodNotFound, banUnknownMessage] = normalizeStrictOptions(strict);
468 const [
469 log_beCalled,
470 log_localError,
471 log_remoteError,
472 log_pretty,
473 log_requestReplay,
474 log_sendLocalStack,
475 ] = normalizeLogOptions(log);
476 const {
477 log: console_log,
478 error: console_error = console_log,
479 debug: console_debug = console_log,
480 groupCollapsed: console_groupCollapsed = console_log,
481 groupEnd: console_groupEnd = console_log,
482 warn: console_warn = console_log,
483 } = (logger || console);
484
485 const requestContext = new Map();
486 const onRequest = async (data) => {
487 if (isThisSideImplementationPending) await awaitThisSideImplementation();
488 else {
489 // not pending
490 if (rejectedThisSideImplementation) return makeErrorObject(rejectedThisSideImplementation, '', data)
491 }
492 let frameworkStack = '';
493 try {
494 const { params, method, id: req_id, remoteStack } = data;
495 // ? We're mapping any method starts with 'rpc.' to a Symbol.for
496 const key = (method.startsWith('rpc.') ? Symbol.for(method) : method);
497 const executor =
498 resolvedThisSideImplementationValue && (resolvedThisSideImplementationValue )[key];
499 if (!isFunction(executor)) {
500 if (!banMethodNotFound) {
501 if (log_localError) console_debug('Missing method', key, data);
502 return
503 } else return ErrorResponseMethodNotFound(req_id)
504 }
505 const args = isArray(params) ? params : [params];
506 frameworkStack = removeStackHeader(new Error().stack);
507 const promise = new Promise((resolve) => resolve(executor.apply(resolvedThisSideImplementationValue, args)));
508 if (log_beCalled) {
509 if (log_pretty) {
510 const logArgs = [
511 `${logKey}.%c${method}%c(${args.map(() => '%o').join(', ')}%c)\n%o %c@${req_id}`,
512 'color: #d2c057',
513 '',
514 ...args,
515 '',
516 promise,
517 'color: gray; font-style: italic;',
518 ];
519 if (log_requestReplay) {
520 // This function will be logged to the console so it must be 1 line
521 // prettier-ignore
522 const replay = () => { debugger; return executor.apply(resolvedThisSideImplementationValue, args) };
523 replay.toString = replayFunction;
524 logArgs.push(replay);
525 }
526 if (remoteStack) {
527 console_groupCollapsed(...logArgs);
528 console_log(remoteStack);
529 console_groupEnd();
530 } else console_log(...logArgs);
531 } else console_log(`${logKey}.${method}(${[...args].toString()}) @${req_id}`);
532 }
533 const result = await promise;
534 if (result === AsyncCallIgnoreResponse) return
535 return SuccessResponse(req_id, await promise)
536 } catch (e) {
537 return makeErrorObject(e, frameworkStack, data)
538 }
539 };
540 const onResponse = async (data) => {
541 let errorMessage = '',
542 remoteErrorStack = '',
543 errorCode = 0,
544 errorType = ERROR;
545 if (hasKey(data, 'error')) {
546 const e = data.error;
547 errorMessage = e.message;
548 errorCode = e.code;
549 const detail = e.data;
550
551 if (isObject(detail) && hasKey(detail, 'stack') && isString(detail.stack)) remoteErrorStack = detail.stack;
552 else remoteErrorStack = '<remote stack not available>';
553
554 if (isObject(detail) && hasKey(detail, 'type') && isString(detail.type)) errorType = detail.type;
555 else errorType = ERROR;
556
557 if (log_remoteError)
558 log_pretty
559 ? console_error(
560 `${errorType}: ${errorMessage}(${errorCode}) %c@${data.id}\n%c${remoteErrorStack}`,
561 'color: gray',
562 '',
563 )
564 : console_error(`${errorType}: ${errorMessage}(${errorCode}) @${data.id}\n${remoteErrorStack}`);
565 }
566 if (data.id === null || data.id === undefined$1) return
567 const { f: [resolve, reject] = [null, null], stack: localErrorStack = '' } = requestContext.get(data.id) || {};
568 if (!resolve || !reject) return // drop this response
569 requestContext.delete(data.id);
570 if (hasKey(data, 'error')) {
571 reject(
572 RecoverError(
573 errorType,
574 errorMessage,
575 errorCode,
576 // ? We use \u0430 which looks like "a" to prevent browser think "at AsyncCall" is a real stack
577 remoteErrorStack + '\n \u0430t AsyncCall (rpc) \n' + localErrorStack,
578 ),
579 );
580 } else {
581 resolve(data.result);
582 }
583 return
584 };
585 const rawMessageReceiver = async (_) => {
586 let data;
587 let result = undefined$1;
588 try {
589 data = await deserialization(_);
590 if (isJSONRPCObject(data)) {
591 return (result = await handleSingleMessage(data))
592 } else if (isArray(data) && data.every(isJSONRPCObject) && data.length !== 0) {
593 return Promise.all(data.map(handleSingleMessage))
594 } else {
595 if (banUnknownMessage) {
596 let id = (data ).id;
597 if (id === undefined$1) id = null;
598 return ErrorResponseInvalidRequest(id)
599 } else {
600 // ? Ignore this message. The message channel maybe also used to transfer other message too.
601 return undefined$1
602 }
603 }
604 } catch (e) {
605 if (log_localError) console_error(e, data, result);
606 return ErrorResponseParseError(e, mapError || defaultErrorMapper(e && e.stack))
607 }
608 };
609 const rawMessageSender = async (res) => {
610 if (!res) return
611 if (isArray(res)) {
612 const reply = res.filter((x) => x && hasKey(x, 'id'));
613 if (reply.length === 0) return
614 return serialization(reply)
615 } else {
616 return serialization(res)
617 }
618 };
619 const serialization = (x) => serializer.serialization(x);
620 const deserialization = (x) => serializer.deserialization(x);
621 const isEventBasedChannel = (x) => hasKey(x, 'send') && isFunction(x.send);
622 const isCallbackBasedChannel = (x) =>
623 hasKey(x, 'setup') && isFunction(x.setup);
624
625 if (isCallbackBasedChannel(channel)) {
626 channel.setup(
627 (data) => rawMessageReceiver(data).then(rawMessageSender),
628 (data) => {
629 const _ = deserialization(data);
630 if (isJSONRPCObject(_)) return true
631 return Promise_resolve(_).then(isJSONRPCObject)
632 },
633 );
634 }
635 if (isEventBasedChannel(channel)) {
636 const m = channel;
637 m.on &&
638 m.on((_) =>
639 rawMessageReceiver(_)
640 .then(rawMessageSender)
641 .then((x) => x && m.send(x)),
642 );
643 }
644 function makeErrorObject(e, frameworkStack, data) {
645 if (isObject(e) && hasKey(e, 'stack'))
646 e.stack = frameworkStack
647 .split('\n')
648 .reduce((stack, fstack) => stack.replace(fstack + '\n', ''), '' + e.stack);
649 if (log_localError) console_error(e);
650 return ErrorResponseMapped(data, e, mapError || defaultErrorMapper(log_sendLocalStack ? e.stack : undefined$1))
651 }
652
653 async function sendPayload(payload, removeQueueR = false) {
654 if (removeQueueR) payload = [...(payload )];
655 const data = await serialization(payload);
656 return channel.send(data)
657 }
658 function rejectsQueue(queue, error) {
659 for (const x of queue) {
660 if (hasKey(x, 'id')) {
661 const ctx = requestContext.get(x.id);
662 ctx && ctx.f[1](error);
663 }
664 }
665 }
666 const handleSingleMessage = async (
667 data,
668 ) => {
669 if (hasKey(data, 'method')) {
670 const r = onRequest(data);
671 if (hasKey(data, 'id')) return r
672 try {
673 await r;
674 } catch (e2) {}
675 return undefined$1 // Does not care about return result for notifications
676 }
677 return onResponse(data)
678 };
679 return new Proxy({ __proto__: null } , {
680 get(cache, method) {
681 if (method === 'then') {
682 if (thenable === undefined$1) {
683 console_warn(
684 makeHostedMessage(
685 Err_Then_is_accessed_on_local_implementation_Please_explicitly_mark_if_it_is_thenable_in_the_options,
686 new TypeError('RPC used as Promise: '),
687 ),
688 );
689 }
690 if (thenable !== true) return undefined$1
691 }
692 if (isString(method) && cache[method]) return cache[method]
693 const factory = (notify) => (...params) => {
694 let stack = removeStackHeader(new Error().stack);
695 let queue = undefined$1;
696 if (method === AsyncCallBatch) {
697 queue = params.shift();
698 method = params.shift();
699 }
700 if (typeof method === 'symbol') {
701 const RPCInternalMethod = Symbol.keyFor(method) || (method ).description;
702 if (RPCInternalMethod) {
703 if (RPCInternalMethod.startsWith('rpc.')) method = RPCInternalMethod;
704 else return Promise_reject(new TypeError('Not start with rpc.'))
705 }
706 } else if (method.startsWith('rpc.'))
707 return Promise_reject(
708 makeHostedMessage(Err_Cannot_call_method_starts_with_rpc_dot_directly, new TypeError()),
709 )
710 return new Promise((resolve, reject) => {
711 if (preferLocalImplementation && !isThisSideImplementationPending && isString(method)) {
712 const localImpl =
713 resolvedThisSideImplementationValue && (resolvedThisSideImplementationValue )[method];
714 if (isFunction(localImpl)) return resolve(localImpl(...params))
715 }
716 const id = idGenerator();
717 const [param0] = params;
718 const sendingStack = log_sendLocalStack ? stack : '';
719 const param =
720 parameterStructures === 'by-name' && params.length === 1 && isObject(param0) ? param0 : params;
721 const request = Request(notify ? undefined$1 : id, method , param, sendingStack);
722 if (queue) {
723 queue.push(request);
724 if (!queue.r) queue.r = [() => sendPayload(queue, true), (e) => rejectsQueue(queue, e)];
725 } else sendPayload(request).catch(reject);
726 if (notify) return resolve()
727 requestContext.set(id, {
728 f: [resolve, reject],
729 stack,
730 });
731 })
732 };
733 const f = factory(false);
734 // @ts-ignore
735 f[AsyncCallNotify] = factory(true);
736 // @ts-ignore
737 f[AsyncCallNotify][AsyncCallNotify] = f[AsyncCallNotify];
738 isString(method) && Object.defineProperty(cache, method, { value: f, configurable: true });
739 return f
740 },
741 })
742 }
743 // Assume a console object in global if there is no custom logger provided
744
745 function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }/**
746 * See the document at https://github.com/Jack-Works/async-call/
747 */
748
749 const i = 'rpc.async-iterator.';
750 // ! side effect
751 const AsyncIteratorStart = Symbol.for(i + 'start');
752 const AsyncIteratorNext = Symbol.for(i + 'next');
753 const AsyncIteratorReturn = Symbol.for(i + 'return');
754 const AsyncIteratorThrow = Symbol.for(i + 'throw');
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803 /**
804 * The async generator version of the AsyncCall
805 * @param thisSideImplementation - The implementation when this AsyncCall acts as a JSON RPC server.
806 * @param options - {@link AsyncCallOptions}
807 * @typeParam OtherSideImplementedFunctions - The type of the API that server expose. For any function on this interface, AsyncCall will convert it to the Promised type.
808 * @remarks
809 * Warning: Due to technical limitation, AsyncGeneratorCall will leak memory. Use it at your own risk.
810 *
811 * To use AsyncGeneratorCall, the server and the client MUST support the following JSON RPC internal methods which is pre ECMAScript async generator semantics:
812 *
813 * - `rpc.async-iterator.start`
814 *
815 * - `rpc.async-iterator.next`
816 *
817 * - `rpc.async-iterator.return`
818 *
819 * - `rpc.async-iterator.throw`
820 *
821 * @example
822 * ```ts
823 * const server = {
824 * async *generator() {
825 * let last = 0
826 * while (true) yield last++
827 * },
828 * }
829 * type Server = typeof server
830 * const serverRPC = AsyncGeneratorCall<Server>({}, { channel })
831 * async function main() {
832 * for await (const x of serverRPC.generator()) {
833 * console.log('Server yielded number', x)
834 * }
835 * }
836 * ```
837 * @public
838 */
839 function AsyncGeneratorCall(
840 thisSideImplementation,
841 options,
842 ) {
843 const iterators = new Map();
844 const [methodNotFound] = normalizeStrictOptions(_nullishCoalesce(options.strict, () => ( true)));
845 const { idGenerator = generateRandomID } = options;
846 const findIterator = (
847 id,
848 next,
849 ) => {
850 const it = iterators.get(id);
851 if (!it) {
852 if (methodNotFound)
853 throw makeHostedMessage(Err_Cannot_find_a_running_iterator_with_given_ID, new Error(`Iterator ${id}, `))
854 else return AsyncCallIgnoreResponse
855 }
856 const result = next(it);
857 isFinished(result, () => iterators.delete(id));
858 return result
859 };
860 const server = {
861 async [AsyncIteratorStart](method, args) {
862 const iteratorGenerator = ((await thisSideImplementation) )[method];
863 if (!isFunction(iteratorGenerator)) {
864 if (methodNotFound) throw new TypeError(method + ' is not a function')
865 else return AsyncCallIgnoreResponse
866 }
867 const iterator = iteratorGenerator(...args);
868 const id = idGenerator();
869 iterators.set(id, iterator);
870 return Promise_resolve(id)
871 },
872 [AsyncIteratorNext](id, val) {
873 return findIterator(id, (it) => it.next(val ))
874 },
875 [AsyncIteratorReturn](id, val) {
876 return findIterator(id, (it) => isFunction(it.return) && it.return(val))
877 },
878 [AsyncIteratorThrow](id, val) {
879 return findIterator(id, (it) => isFunction(it.throw) && it.throw(val))
880 },
881 };
882 const remote = AsyncCall(server, options);
883 const proxyTrap = (cache, key) => {
884 if (!isString(key))
885 throw makeHostedMessage(Err_Only_string_can_be_the_RPC_method_name, new TypeError(''))
886 if (cache[key]) return cache[key]
887 const f = (...args) => {
888 const id = remote[AsyncIteratorStart](key, args);
889 return new _AsyncGenerator(remote, id)
890 };
891 Object.defineProperty(cache, key, { value: f, configurable: true });
892 return f
893 };
894 return new Proxy({ __proto__: null }, { get: proxyTrap })
895 }
896 class _AsyncGenerator {
897 /** done? */
898 __init() {this.d = false;}
899 /** check */
900 __init2() {this.c = async (val) => {
901 await isFinished(val, () => (this.d = true));
902 return val
903 };}
904 /**
905 * @param r Remote Implementation
906 * @param i id
907 */
908 constructor( r, i) {this.r = r;this.i = i;_AsyncGenerator.prototype.__init.call(this);_AsyncGenerator.prototype.__init2.call(this);}
909 async return(val) {
910 if (this.d) return makeIteratorResult(true, val)
911 return this.c(this.r[AsyncIteratorReturn](await this.i, val))
912 }
913 async next(val) {
914 if (this.d) return makeIteratorResult(true)
915 return this.c(this.r[AsyncIteratorNext](await this.i, val))
916 }
917 async throw(val) {
918 if (!this.d) return this.c(this.r[AsyncIteratorThrow](await this.i, val))
919 throw val
920 }
921 // Inherited from AsyncGeneratorPrototype
922
923 }
924 // ! side effect
925 const EmptyAsyncGenerator = async function* () {};
926 const AsyncGeneratorConstructor = EmptyAsyncGenerator.constructor;
927 const AsyncGeneratorConstructorPrototype = AsyncGeneratorConstructor.prototype;
928 Object_setPrototypeOf(_AsyncGenerator, AsyncGeneratorConstructorPrototype);
929 const AsyncGeneratorPrototype = Object.getPrototypeOf(EmptyAsyncGenerator());
930 Object_setPrototypeOf(_AsyncGenerator.prototype, AsyncGeneratorPrototype);
931
932 const isFinished = async (result, cb) => {
933 try {
934 const x = await result;
935 x && x.done && cb();
936 } catch (e) {}
937 };
938
939 const makeIteratorResult = (done, value = undefined) => ({
940 done,
941 value,
942 });
943
944 exports.AsyncCall = AsyncCall;
945 exports.AsyncGeneratorCall = AsyncGeneratorCall;
946 exports.JSONSerialization = JSONSerialization;
947 exports.NoSerialization = NoSerialization;
948 exports.batch = batch;
949 exports.notify = notify;
950
951 Object.defineProperty(exports, '__esModule', { value: true });
952
953})));
954//# sourceMappingURL=full.js.map