@nmtjs/client 0.15.3 → 0.16.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/client.d.ts +68 -0
  2. package/dist/client.js +101 -0
  3. package/dist/client.js.map +1 -0
  4. package/dist/clients/runtime.d.ts +6 -12
  5. package/dist/clients/runtime.js +58 -57
  6. package/dist/clients/runtime.js.map +1 -1
  7. package/dist/clients/static.d.ts +4 -9
  8. package/dist/clients/static.js +20 -20
  9. package/dist/clients/static.js.map +1 -1
  10. package/dist/core.d.ts +36 -83
  11. package/dist/core.js +315 -690
  12. package/dist/core.js.map +1 -1
  13. package/dist/events.d.ts +1 -2
  14. package/dist/events.js +74 -11
  15. package/dist/events.js.map +1 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +4 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/layers/ping.d.ts +6 -0
  20. package/dist/layers/ping.js +65 -0
  21. package/dist/layers/ping.js.map +1 -0
  22. package/dist/layers/rpc.d.ts +19 -0
  23. package/dist/layers/rpc.js +557 -0
  24. package/dist/layers/rpc.js.map +1 -0
  25. package/dist/layers/streams.d.ts +25 -0
  26. package/dist/layers/streams.js +207 -0
  27. package/dist/layers/streams.js.map +1 -0
  28. package/dist/plugins/browser.js +28 -9
  29. package/dist/plugins/browser.js.map +1 -1
  30. package/dist/plugins/heartbeat.js +10 -10
  31. package/dist/plugins/heartbeat.js.map +1 -1
  32. package/dist/plugins/index.d.ts +1 -1
  33. package/dist/plugins/index.js +0 -1
  34. package/dist/plugins/index.js.map +1 -1
  35. package/dist/plugins/logging.js.map +1 -1
  36. package/dist/plugins/reconnect.js +11 -94
  37. package/dist/plugins/reconnect.js.map +1 -1
  38. package/dist/plugins/types.d.ts +27 -11
  39. package/dist/streams.js.map +1 -1
  40. package/dist/transformers.js.map +1 -1
  41. package/dist/transport.d.ts +49 -31
  42. package/dist/types.d.ts +23 -13
  43. package/dist/types.js.map +1 -1
  44. package/package.json +9 -10
  45. package/src/client.ts +232 -0
  46. package/src/clients/runtime.ts +93 -79
  47. package/src/clients/static.ts +46 -38
  48. package/src/core.ts +408 -901
  49. package/src/events.ts +113 -14
  50. package/src/index.ts +4 -0
  51. package/src/layers/ping.ts +99 -0
  52. package/src/layers/rpc.ts +767 -0
  53. package/src/layers/streams.ts +320 -0
  54. package/src/plugins/browser.ts +39 -9
  55. package/src/plugins/heartbeat.ts +10 -10
  56. package/src/plugins/index.ts +8 -1
  57. package/src/plugins/reconnect.ts +12 -119
  58. package/src/plugins/types.ts +30 -13
  59. package/src/transport.ts +75 -46
  60. package/src/types.ts +34 -19
@@ -0,0 +1,767 @@
1
+ import type { Future } from '@nmtjs/common'
2
+ import type { ServerMessageTypePayload } from '@nmtjs/protocol/client'
3
+ import { anyAbortSignal, createFuture, MAX_UINT32, noopFn } from '@nmtjs/common'
4
+ import {
5
+ ClientMessageType,
6
+ ConnectionType,
7
+ ErrorCode,
8
+ ProtocolBlob,
9
+ ServerMessageType,
10
+ } from '@nmtjs/protocol'
11
+ import { ProtocolError, ProtocolServerRPCStream } from '@nmtjs/protocol/client'
12
+
13
+ import type { ClientCore } from '../core.ts'
14
+ import type { BaseClientTransformer } from '../transformers.ts'
15
+ import type { ClientCallOptions } from '../types.ts'
16
+ import type { StreamLayerApi } from './streams.ts'
17
+ import { ServerStreams } from '../streams.ts'
18
+
19
+ export type ProtocolClientCall = Future<any> & {
20
+ procedure: string
21
+ signal?: AbortSignal
22
+ cleanup?: () => void
23
+ }
24
+
25
+ export interface RpcLayerApi {
26
+ call(
27
+ procedure: string,
28
+ payload: any,
29
+ options?: ClientCallOptions,
30
+ ): Promise<any>
31
+ readonly pendingCallCount: number
32
+ readonly activeStreamCount: number
33
+ }
34
+
35
+ const toReasonString = (reason: unknown) => {
36
+ if (typeof reason === 'string') return reason
37
+ if (reason === undefined || reason === null) return undefined
38
+ return String(reason)
39
+ }
40
+
41
+ const toAbortError = (signal: AbortSignal) => {
42
+ return new ProtocolError(ErrorCode.ClientRequestError, String(signal.reason))
43
+ }
44
+
45
+ const waitForConnect = async (core: ClientCore, signal?: AbortSignal) => {
46
+ if (!core.shouldConnectOnCall()) return
47
+
48
+ if (signal?.aborted) {
49
+ throw toAbortError(signal)
50
+ }
51
+
52
+ const connectPromise = core.connect()
53
+
54
+ if (!signal) {
55
+ await connectPromise
56
+ return
57
+ }
58
+
59
+ await new Promise<void>((resolve, reject) => {
60
+ const onAbort = () => {
61
+ reject(toAbortError(signal))
62
+ }
63
+
64
+ signal.addEventListener('abort', onAbort, { once: true })
65
+
66
+ connectPromise.then(resolve, reject).finally(() => {
67
+ signal.removeEventListener('abort', onAbort)
68
+ })
69
+ })
70
+ }
71
+
72
+ const ensureConnectedForCall = async (
73
+ core: ClientCore,
74
+ signal?: AbortSignal,
75
+ ) => {
76
+ if (!core.autoConnect) return
77
+
78
+ if (core.state !== 'connected') {
79
+ await waitForConnect(core, signal)
80
+ }
81
+
82
+ if (core.state !== 'connected') {
83
+ throw new ProtocolError(
84
+ ErrorCode.ConnectionError,
85
+ 'Client is not connected',
86
+ )
87
+ }
88
+ }
89
+
90
+ const waitForConnected = (core: ClientCore, signal?: AbortSignal) => {
91
+ if (core.state === 'connected') return Promise.resolve()
92
+
93
+ return new Promise<void>((resolve, reject) => {
94
+ if (signal?.aborted) {
95
+ reject(signal.reason)
96
+ return
97
+ }
98
+
99
+ const offConnected = core.once('connected', () => {
100
+ signal?.removeEventListener('abort', onAbort)
101
+ resolve()
102
+ })
103
+
104
+ const onAbort = () => {
105
+ offConnected()
106
+ reject(signal?.reason)
107
+ }
108
+
109
+ signal?.addEventListener('abort', onAbort, { once: true })
110
+ })
111
+ }
112
+
113
+ async function* reconnectingAsyncIterable<T>(
114
+ core: ClientCore,
115
+ initialIterable: AsyncIterable<T>,
116
+ callFn: () => Promise<AsyncIterable<T>>,
117
+ signal?: AbortSignal,
118
+ ): AsyncGenerator<T> {
119
+ let iterable: AsyncIterable<T> | null = initialIterable
120
+
121
+ while (!signal?.aborted) {
122
+ try {
123
+ const currentIterable = iterable ?? (await callFn())
124
+ iterable = null
125
+
126
+ for await (const item of currentIterable) {
127
+ yield item
128
+ }
129
+ return
130
+ } catch (error) {
131
+ iterable = null
132
+
133
+ if (signal?.aborted) throw error
134
+
135
+ if (
136
+ error instanceof ProtocolError &&
137
+ error.code === ErrorCode.ConnectionError
138
+ ) {
139
+ await waitForConnected(core, signal)
140
+ continue
141
+ }
142
+
143
+ throw error
144
+ }
145
+ }
146
+ }
147
+
148
+ const createManagedAsyncIterable = <T>(
149
+ iterable: AsyncIterable<T>,
150
+ options: {
151
+ onDone?: () => void
152
+ onReturn?: (value: unknown) => void
153
+ onThrow?: (error: unknown) => void
154
+ },
155
+ ): AsyncIterable<T> => {
156
+ return {
157
+ [Symbol.asyncIterator]() {
158
+ const iterator = iterable[Symbol.asyncIterator]()
159
+ let settled = false
160
+
161
+ const finish = () => {
162
+ if (settled) return
163
+ settled = true
164
+ options.onDone?.()
165
+ }
166
+
167
+ return {
168
+ async next() {
169
+ const result = await iterator.next()
170
+ if (result.done) {
171
+ finish()
172
+ }
173
+ return result
174
+ },
175
+ async return(value) {
176
+ options.onReturn?.(value)
177
+ finish()
178
+ return iterator.return?.(value) ?? { done: true, value }
179
+ },
180
+ async throw(error) {
181
+ options.onThrow?.(error)
182
+ finish()
183
+ return iterator.throw?.(error) ?? Promise.reject(error)
184
+ },
185
+ }
186
+ },
187
+ }
188
+ }
189
+
190
+ export const createRpcLayer = (
191
+ core: ClientCore,
192
+ streams: StreamLayerApi,
193
+ transformer: BaseClientTransformer,
194
+ options: { timeout?: number; safe?: boolean } = {},
195
+ ): RpcLayerApi => {
196
+ const calls = new Map<number, ProtocolClientCall>()
197
+ const rpcStreams = new ServerStreams<ProtocolServerRPCStream>()
198
+
199
+ let callId = 0
200
+
201
+ const nextCallId = () => {
202
+ if (callId >= MAX_UINT32) {
203
+ callId = 0
204
+ }
205
+
206
+ return callId++
207
+ }
208
+
209
+ const handleRPCResponseMessage = (
210
+ message: ServerMessageTypePayload[ServerMessageType.RpcResponse],
211
+ ) => {
212
+ const call = calls.get(message.callId)
213
+ if (!call) return
214
+
215
+ if (message.error) {
216
+ core.emitClientEvent({
217
+ kind: 'rpc_error',
218
+ timestamp: Date.now(),
219
+ callId: message.callId,
220
+ procedure: call.procedure,
221
+ error: message.error,
222
+ })
223
+
224
+ call.reject(
225
+ new ProtocolError(
226
+ message.error.code,
227
+ message.error.message,
228
+ message.error.data,
229
+ ),
230
+ )
231
+ return
232
+ }
233
+
234
+ try {
235
+ const transformed = transformer.decode(call.procedure, message.result)
236
+ core.emitClientEvent({
237
+ kind: 'rpc_response',
238
+ timestamp: Date.now(),
239
+ callId: message.callId,
240
+ procedure: call.procedure,
241
+ body: transformed,
242
+ })
243
+ call.resolve(transformed)
244
+ } catch (error) {
245
+ core.emitClientEvent({
246
+ kind: 'rpc_error',
247
+ timestamp: Date.now(),
248
+ callId: message.callId,
249
+ procedure: call.procedure,
250
+ error,
251
+ })
252
+ call.reject(
253
+ new ProtocolError(
254
+ ErrorCode.ClientRequestError,
255
+ 'Unable to decode response',
256
+ error,
257
+ ),
258
+ )
259
+ }
260
+ }
261
+
262
+ const handleRPCStreamResponseMessage = (
263
+ message: ServerMessageTypePayload[ServerMessageType.RpcStreamResponse],
264
+ ) => {
265
+ const call = calls.get(message.callId)
266
+
267
+ if (message.error) {
268
+ if (!call) return
269
+
270
+ core.emitClientEvent({
271
+ kind: 'rpc_error',
272
+ timestamp: Date.now(),
273
+ callId: message.callId,
274
+ procedure: call.procedure,
275
+ error: message.error,
276
+ })
277
+
278
+ call.reject(
279
+ new ProtocolError(
280
+ message.error.code,
281
+ message.error.message,
282
+ message.error.data,
283
+ ),
284
+ )
285
+ return
286
+ }
287
+
288
+ if (!call) {
289
+ if (!core.messageContext) return
290
+
291
+ const buffer = core.protocol.encodeMessage(
292
+ core.messageContext,
293
+ ClientMessageType.RpcAbort,
294
+ { callId: message.callId },
295
+ )
296
+
297
+ core.send(buffer).catch(noopFn)
298
+ return
299
+ }
300
+
301
+ core.emitClientEvent({
302
+ kind: 'rpc_response',
303
+ timestamp: Date.now(),
304
+ callId: message.callId,
305
+ procedure: call.procedure,
306
+ stream: true,
307
+ })
308
+
309
+ const { procedure, signal } = call
310
+ const stream = new ProtocolServerRPCStream({
311
+ start: (controller) => {
312
+ if (!signal) return
313
+
314
+ if (signal.aborted) {
315
+ controller.error(signal.reason)
316
+ return
317
+ }
318
+
319
+ const onAbort = () => {
320
+ controller.error(signal.reason)
321
+
322
+ if (rpcStreams.has(message.callId)) {
323
+ void rpcStreams.abort(message.callId).catch(noopFn)
324
+ if (core.messageContext) {
325
+ const buffer = core.protocol.encodeMessage(
326
+ core.messageContext,
327
+ ClientMessageType.RpcAbort,
328
+ {
329
+ callId: message.callId,
330
+ reason: toReasonString(signal.reason),
331
+ },
332
+ )
333
+ core.send(buffer).catch(noopFn)
334
+ }
335
+ }
336
+ }
337
+
338
+ signal.addEventListener('abort', onAbort, { once: true })
339
+ call.cleanup = () => {
340
+ signal.removeEventListener('abort', onAbort)
341
+ }
342
+ },
343
+ transform: (chunk) => {
344
+ return transformer.decode(procedure, core.format.decode(chunk))
345
+ },
346
+ readableStrategy: { highWaterMark: 0 },
347
+ })
348
+
349
+ rpcStreams.add(message.callId, stream)
350
+ call.resolve(stream)
351
+ }
352
+
353
+ const handleTransportErrorResponse = (
354
+ callId: number,
355
+ response: Extract<
356
+ Awaited<ReturnType<ClientCore['transportCall']>>,
357
+ { type: 'error' }
358
+ >,
359
+ ) => {
360
+ const call = calls.get(callId)
361
+ if (!call) return
362
+
363
+ let error: ProtocolError
364
+
365
+ try {
366
+ const decoded = core.format.decode(response.error) as {
367
+ code?: string
368
+ message?: string
369
+ data?: unknown
370
+ }
371
+
372
+ error = new ProtocolError(
373
+ decoded.code || ErrorCode.ClientRequestError,
374
+ decoded.message || response.statusText || 'Request failed',
375
+ decoded.data,
376
+ )
377
+ } catch {
378
+ error = new ProtocolError(
379
+ ErrorCode.ClientRequestError,
380
+ response.statusText
381
+ ? `HTTP ${response.status ?? ''}: ${response.statusText}`.trim()
382
+ : 'Request failed',
383
+ )
384
+ }
385
+
386
+ core.emitClientEvent({
387
+ kind: 'rpc_error',
388
+ timestamp: Date.now(),
389
+ callId,
390
+ procedure: call.procedure,
391
+ error,
392
+ })
393
+
394
+ call.reject(error)
395
+ }
396
+
397
+ const handleCallResponse = (
398
+ currentCallId: number,
399
+ response: Awaited<ReturnType<ClientCore['transportCall']>>,
400
+ ) => {
401
+ const call = calls.get(currentCallId)
402
+
403
+ if (response.type === 'error') {
404
+ handleTransportErrorResponse(currentCallId, response)
405
+ return
406
+ }
407
+
408
+ if (response.type === 'rpc_stream') {
409
+ if (!call) {
410
+ response.stream.cancel().catch(noopFn)
411
+ return
412
+ }
413
+
414
+ core.emitClientEvent({
415
+ kind: 'rpc_response',
416
+ timestamp: Date.now(),
417
+ callId: currentCallId,
418
+ procedure: call.procedure,
419
+ stream: true,
420
+ })
421
+
422
+ const reader = response.stream.getReader()
423
+ const { signal } = call
424
+ let onAbort: (() => void) | undefined
425
+
426
+ const stream = new ProtocolServerRPCStream({
427
+ start: (controller) => {
428
+ if (!signal) return
429
+
430
+ onAbort = () => {
431
+ controller.error(signal.reason)
432
+ reader.cancel(signal.reason).catch(noopFn)
433
+ void rpcStreams.abort(currentCallId).catch(noopFn)
434
+ }
435
+
436
+ if (signal.aborted) {
437
+ onAbort()
438
+ } else {
439
+ signal.addEventListener('abort', onAbort, { once: true })
440
+ }
441
+ },
442
+ transform: (chunk) => {
443
+ return transformer.decode(call.procedure, core.format.decode(chunk))
444
+ },
445
+ readableStrategy: { highWaterMark: 0 },
446
+ })
447
+
448
+ rpcStreams.add(currentCallId, stream)
449
+ call.resolve(stream)
450
+
451
+ void (async () => {
452
+ try {
453
+ while (true) {
454
+ const { done, value } = await reader.read()
455
+ if (done) break
456
+ await rpcStreams.push(currentCallId, value)
457
+ }
458
+ await rpcStreams.end(currentCallId)
459
+ } catch {
460
+ await rpcStreams.abort(currentCallId).catch(noopFn)
461
+ } finally {
462
+ reader.releaseLock()
463
+ if (signal && onAbort) {
464
+ signal.removeEventListener('abort', onAbort)
465
+ }
466
+ }
467
+ })()
468
+
469
+ return
470
+ }
471
+
472
+ if (response.type === 'blob') {
473
+ if (!call) {
474
+ response.source.cancel().catch(noopFn)
475
+ return
476
+ }
477
+
478
+ core.emitClientEvent({
479
+ kind: 'rpc_response',
480
+ timestamp: Date.now(),
481
+ callId: currentCallId,
482
+ procedure: call.procedure,
483
+ stream: true,
484
+ })
485
+
486
+ const { blob } = streams.addServerBlobStream(response.metadata, {
487
+ start: (stream, { signal } = {}) => {
488
+ response.source.pipeTo(stream.writable, { signal }).catch(noopFn)
489
+ },
490
+ })
491
+ call.resolve(blob)
492
+ return
493
+ }
494
+
495
+ if (!call) return
496
+
497
+ try {
498
+ const decodedPayload =
499
+ response.result.byteLength === 0
500
+ ? undefined
501
+ : core.format.decode(response.result)
502
+
503
+ const transformed = transformer.decode(call.procedure, decodedPayload)
504
+ core.emitClientEvent({
505
+ kind: 'rpc_response',
506
+ timestamp: Date.now(),
507
+ callId: currentCallId,
508
+ procedure: call.procedure,
509
+ body: transformed,
510
+ })
511
+ call.resolve(transformed)
512
+ } catch (error) {
513
+ core.emitClientEvent({
514
+ kind: 'rpc_error',
515
+ timestamp: Date.now(),
516
+ callId: currentCallId,
517
+ procedure: call.procedure,
518
+ error,
519
+ })
520
+ call.reject(
521
+ new ProtocolError(
522
+ ErrorCode.ClientRequestError,
523
+ 'Unable to decode response',
524
+ error,
525
+ ),
526
+ )
527
+ }
528
+ }
529
+
530
+ core.on('message', (message: any) => {
531
+ switch (message.type) {
532
+ case ServerMessageType.RpcResponse:
533
+ handleRPCResponseMessage(message)
534
+ break
535
+ case ServerMessageType.RpcStreamResponse:
536
+ handleRPCStreamResponseMessage(message)
537
+ break
538
+ case ServerMessageType.RpcStreamChunk:
539
+ core.emitStreamEvent({
540
+ direction: 'incoming',
541
+ streamType: 'rpc',
542
+ action: 'push',
543
+ callId: message.callId,
544
+ byteLength: message.chunk.byteLength,
545
+ })
546
+ void rpcStreams.push(message.callId, message.chunk)
547
+ break
548
+ case ServerMessageType.RpcStreamEnd:
549
+ calls.get(message.callId)?.cleanup?.()
550
+ core.emitStreamEvent({
551
+ direction: 'incoming',
552
+ streamType: 'rpc',
553
+ action: 'end',
554
+ callId: message.callId,
555
+ })
556
+ void rpcStreams.end(message.callId)
557
+ calls.delete(message.callId)
558
+ break
559
+ case ServerMessageType.RpcStreamAbort:
560
+ calls.get(message.callId)?.cleanup?.()
561
+ core.emitStreamEvent({
562
+ direction: 'incoming',
563
+ streamType: 'rpc',
564
+ action: 'abort',
565
+ callId: message.callId,
566
+ reason: message.reason,
567
+ })
568
+ void rpcStreams.abort(message.callId)
569
+ calls.delete(message.callId)
570
+ break
571
+ }
572
+ })
573
+
574
+ core.on('disconnected', (reason) => {
575
+ const error = new ProtocolError(ErrorCode.ConnectionError, 'Disconnected', {
576
+ reason,
577
+ })
578
+
579
+ for (const call of calls.values()) {
580
+ call.cleanup?.()
581
+ call.reject(error)
582
+ }
583
+ calls.clear()
584
+ void rpcStreams.clear(error).catch(noopFn)
585
+ })
586
+
587
+ const callInternal = async (
588
+ procedure: string,
589
+ payload: any,
590
+ callOptions: ClientCallOptions = {},
591
+ ) => {
592
+ const timeout = callOptions.timeout ?? options.timeout
593
+ const controller = new AbortController()
594
+
595
+ const signals: AbortSignal[] = [controller.signal]
596
+
597
+ if (timeout) signals.push(AbortSignal.timeout(timeout))
598
+ if (callOptions.signal) signals.push(callOptions.signal)
599
+ if (core.connectionSignal) signals.push(core.connectionSignal)
600
+
601
+ const signal = signals.length ? anyAbortSignal(...signals) : undefined
602
+ const currentCallId = nextCallId()
603
+ const call = createFuture() as ProtocolClientCall
604
+ call.procedure = procedure
605
+ call.signal = signal
606
+
607
+ calls.set(currentCallId, call)
608
+ core.emitClientEvent({
609
+ kind: 'rpc_request',
610
+ timestamp: Date.now(),
611
+ callId: currentCallId,
612
+ procedure,
613
+ body: payload,
614
+ })
615
+
616
+ if (signal?.aborted) {
617
+ call.reject(toAbortError(signal))
618
+ } else {
619
+ try {
620
+ if (core.autoConnect) {
621
+ await ensureConnectedForCall(core, signal)
622
+ }
623
+
624
+ if (signal?.aborted) {
625
+ throw toAbortError(signal)
626
+ }
627
+
628
+ signal?.addEventListener(
629
+ 'abort',
630
+ () => {
631
+ call.reject(toAbortError(signal))
632
+
633
+ if (
634
+ core.transportType === ConnectionType.Bidirectional &&
635
+ core.messageContext
636
+ ) {
637
+ const buffer = core.protocol.encodeMessage(
638
+ core.messageContext,
639
+ ClientMessageType.RpcAbort,
640
+ {
641
+ callId: currentCallId,
642
+ reason: toReasonString(signal.reason),
643
+ },
644
+ )
645
+ core.send(buffer).catch(noopFn)
646
+ }
647
+ },
648
+ { once: true },
649
+ )
650
+
651
+ const transformedPayload = transformer.encode(procedure, payload)
652
+
653
+ if (core.transportType === ConnectionType.Bidirectional) {
654
+ if (!core.messageContext) {
655
+ throw new ProtocolError(
656
+ ErrorCode.ConnectionError,
657
+ 'Client is not connected',
658
+ )
659
+ }
660
+
661
+ const buffer = core.protocol.encodeMessage(
662
+ core.messageContext,
663
+ ClientMessageType.Rpc,
664
+ { callId: currentCallId, procedure, payload: transformedPayload },
665
+ )
666
+
667
+ await core.send(buffer, signal)
668
+ } else {
669
+ const blob =
670
+ transformedPayload instanceof ProtocolBlob
671
+ ? {
672
+ source: transformedPayload.source,
673
+ metadata: transformedPayload.metadata,
674
+ }
675
+ : undefined
676
+
677
+ const encodedPayload = blob
678
+ ? new Uint8Array(0)
679
+ : transformedPayload === undefined
680
+ ? new Uint8Array(0)
681
+ : core.format.encode(transformedPayload)
682
+
683
+ const response = await core.transportCall(
684
+ {
685
+ application: core.application,
686
+ auth: core.auth,
687
+ contentType: core.format.contentType,
688
+ },
689
+ { callId: currentCallId, procedure, payload: encodedPayload, blob },
690
+ { signal, streamResponse: callOptions._stream_response },
691
+ )
692
+
693
+ handleCallResponse(currentCallId, response)
694
+ }
695
+ } catch (error) {
696
+ core.emitClientEvent({
697
+ kind: 'rpc_error',
698
+ timestamp: Date.now(),
699
+ callId: currentCallId,
700
+ procedure,
701
+ error,
702
+ })
703
+ call.reject(error)
704
+ }
705
+ }
706
+
707
+ return call.promise
708
+ .then((value) => {
709
+ if (value instanceof ProtocolServerRPCStream) {
710
+ const stream = createManagedAsyncIterable(value, {
711
+ onDone: () => {
712
+ call.cleanup?.()
713
+ },
714
+ onReturn: (reason) => {
715
+ controller.abort(reason)
716
+ },
717
+ onThrow: (error) => {
718
+ controller.abort(error)
719
+ },
720
+ })
721
+
722
+ if (callOptions.autoReconnect) {
723
+ return reconnectingAsyncIterable(
724
+ core,
725
+ stream,
726
+ () =>
727
+ callInternal(procedure, payload, {
728
+ ...callOptions,
729
+ autoReconnect: false,
730
+ }),
731
+ callOptions.signal,
732
+ )
733
+ }
734
+
735
+ return stream
736
+ }
737
+
738
+ controller.abort()
739
+ return value
740
+ })
741
+ .catch((error) => {
742
+ controller.abort()
743
+ throw error
744
+ })
745
+ .finally(() => {
746
+ calls.delete(currentCallId)
747
+ })
748
+ }
749
+
750
+ return {
751
+ async call(procedure, payload, callOptions = {}) {
752
+ if (!options.safe) {
753
+ return callInternal(procedure, payload, callOptions)
754
+ }
755
+
756
+ return callInternal(procedure, payload, callOptions)
757
+ .then((result) => ({ result }))
758
+ .catch((error) => ({ error }))
759
+ },
760
+ get pendingCallCount() {
761
+ return calls.size
762
+ },
763
+ get activeStreamCount() {
764
+ return rpcStreams.size
765
+ },
766
+ }
767
+ }