@nmtjs/client 0.15.3 → 0.16.0-beta.2

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