@actuallab/rpc 12.1.62 → 12.1.69

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.
package/dist/index.cjs CHANGED
@@ -31,14 +31,20 @@ __export(index_exports, {
31
31
  RpcInboundCall: () => RpcInboundCall,
32
32
  RpcInboundCallTracker: () => RpcInboundCallTracker,
33
33
  RpcMessageChannelConnection: () => RpcMessageChannelConnection,
34
+ RpcObjectKind: () => RpcObjectKind,
34
35
  RpcOutboundCall: () => RpcOutboundCall,
35
36
  RpcOutboundCallTracker: () => RpcOutboundCallTracker,
36
37
  RpcPeer: () => RpcPeer,
37
38
  RpcPeerConnectionKind: () => RpcPeerConnectionKind,
38
39
  RpcPeerStateKind: () => RpcPeerStateKind,
39
40
  RpcPeerStateMonitor: () => RpcPeerStateMonitor,
41
+ RpcRemoteObjectTracker: () => RpcRemoteObjectTracker,
40
42
  RpcServerPeer: () => RpcServerPeer,
41
43
  RpcServiceHost: () => RpcServiceHost,
44
+ RpcSharedObjectTracker: () => RpcSharedObjectTracker,
45
+ RpcStream: () => RpcStream,
46
+ RpcStreamSender: () => RpcStreamSender,
47
+ RpcSystemCallHandler: () => RpcSystemCallHandler,
42
48
  RpcSystemCallSender: () => RpcSystemCallSender,
43
49
  RpcSystemCalls: () => RpcSystemCalls,
44
50
  RpcType: () => RpcType,
@@ -52,9 +58,10 @@ __export(index_exports, {
52
58
  getMethodsMeta: () => getMethodsMeta,
53
59
  getServiceMeta: () => getServiceMeta,
54
60
  getStateDescription: () => getStateDescription,
55
- handleSystemCall: () => handleSystemCall,
56
61
  isConnected: () => isConnected,
57
62
  likelyConnected: () => likelyConnected,
63
+ parseStreamRef: () => parseStreamRef,
64
+ resolveStreamRefs: () => resolveStreamRefs,
58
65
  rpcMethod: () => rpcMethod,
59
66
  rpcService: () => rpcService,
60
67
  serializeFrame: () => serializeFrame,
@@ -76,7 +83,12 @@ var RpcSystemCalls = {
76
83
  error: "$sys.Error:1",
77
84
  cancel: "$sys.Cancel:0",
78
85
  keepAlive: "$sys.KeepAlive:1",
79
- reconnect: "$sys.Reconnect:3"
86
+ reconnect: "$sys.Reconnect:3",
87
+ ack: "$sys.Ack:2",
88
+ ackEnd: "$sys.AckEnd:1",
89
+ item: "$sys.I:2",
90
+ batch: "$sys.B:2",
91
+ end: "$sys.End:2"
80
92
  };
81
93
  var ENVELOPE_DELIMITER = "\n";
82
94
  var ARG_DELIMITER = "";
@@ -236,18 +248,18 @@ function createMessageChannelPair() {
236
248
  var RpcType = {
237
249
  object: /* @__PURE__ */ Symbol("object"),
238
250
  stream: /* @__PURE__ */ Symbol("stream"),
251
+ noWait: /* @__PURE__ */ Symbol("noWait"),
239
252
  void: /* @__PURE__ */ Symbol("void")
240
253
  };
241
254
  function wireMethodName(def) {
242
255
  return `${def.serviceName}.${def.name}:${def.wireArgCount}`;
243
256
  }
244
- function defineRpcService(name, methods, options) {
245
- const ctOffset = options?.ctOffset ?? 0;
257
+ function defineRpcService(name, methods) {
246
258
  const map = /* @__PURE__ */ new Map();
247
259
  for (const [key, input] of Object.entries(methods)) {
248
260
  const colonIndex = key.indexOf(":");
249
261
  const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
250
- const wireArgCount = input.args.length + ctOffset;
262
+ const wireArgCount = input.wireArgCount ?? input.args.length + 1;
251
263
  const mapKey = `${cleanName}:${wireArgCount}`;
252
264
  map.set(mapKey, {
253
265
  name: cleanName,
@@ -255,8 +267,8 @@ function defineRpcService(name, methods, options) {
255
267
  argCount: input.args.length,
256
268
  wireArgCount,
257
269
  callTypeId: input.callTypeId ?? 0,
258
- stream: input.stream ?? input.returns === RpcType.stream,
259
- noWait: input.noWait ?? false
270
+ stream: input.returns === RpcType.stream,
271
+ noWait: input.returns === RpcType.noWait
260
272
  });
261
273
  }
262
274
  return { name, methods: map };
@@ -361,40 +373,263 @@ var RpcInboundCallTracker = class {
361
373
  }
362
374
  };
363
375
 
364
- // src/rpc-system-call-handler.ts
365
- function handleSystemCall(message, args, outboundTracker, inboundTracker) {
366
- const method = message.Method;
367
- const relatedId = message.RelatedId ?? 0;
368
- switch (method) {
369
- case RpcSystemCalls.ok: {
370
- const call = outboundTracker.get(relatedId);
371
- if (call !== void 0) {
372
- if (call.removeOnOk) {
373
- outboundTracker.remove(relatedId);
376
+ // src/rpc-stream.ts
377
+ var import_core4 = require("@actuallab/core");
378
+
379
+ // src/rpc-object.ts
380
+ var RpcObjectKind = { Local: 0, Remote: 1 };
381
+
382
+ // src/rpc-stream.ts
383
+ function parseStreamRef(value) {
384
+ if (typeof value !== "string") return null;
385
+ const parts = value.split(",");
386
+ if (parts.length !== 4) return null;
387
+ const hostId = parts[0];
388
+ const localId = parseInt(parts[1], 10);
389
+ const ackPeriod = parseInt(parts[2], 10);
390
+ const ackAdvance = parseInt(parts[3], 10);
391
+ if (isNaN(localId) || isNaN(ackPeriod) || isNaN(ackAdvance)) return null;
392
+ return { hostId, localId, ackPeriod, ackAdvance };
393
+ }
394
+ var RpcStream = class {
395
+ id;
396
+ kind = RpcObjectKind.Remote;
397
+ peer;
398
+ ackPeriod;
399
+ ackAdvance;
400
+ _buffer = [];
401
+ _nextExpectedIndex = 0;
402
+ _consumerWaiting = null;
403
+ _completed = false;
404
+ _completionError = null;
405
+ _disposed = false;
406
+ _started = false;
407
+ _iterating = false;
408
+ _ackSentUpTo = 0;
409
+ constructor(ref, peer) {
410
+ this.id = { hostId: ref.hostId, localId: ref.localId };
411
+ this.peer = peer;
412
+ this.ackPeriod = ref.ackPeriod;
413
+ this.ackAdvance = ref.ackAdvance;
414
+ }
415
+ /** Called by system call handler when a single item arrives ($sys.I). */
416
+ onItem(index, item) {
417
+ if (this._disposed || this._completed) return;
418
+ if (index > this._nextExpectedIndex) {
419
+ this._sendAck(this._nextExpectedIndex, true);
420
+ return;
421
+ }
422
+ if (index < this._nextExpectedIndex) {
423
+ this._maybeSendAck(index + 1);
424
+ return;
425
+ }
426
+ this._buffer.push(item);
427
+ this._nextExpectedIndex = index + 1;
428
+ this._maybeSendAck(this._nextExpectedIndex);
429
+ this._notifyConsumer();
430
+ }
431
+ /** Called by system call handler when a batch arrives ($sys.B). */
432
+ onBatch(index, items) {
433
+ if (this._disposed || this._completed) return;
434
+ if (index > this._nextExpectedIndex) {
435
+ this._sendAck(this._nextExpectedIndex, true);
436
+ return;
437
+ }
438
+ if (index < this._nextExpectedIndex) {
439
+ this._maybeSendAck(index + items.length);
440
+ return;
441
+ }
442
+ for (const item of items) this._buffer.push(item);
443
+ this._nextExpectedIndex = index + items.length;
444
+ this._maybeSendAck(this._nextExpectedIndex);
445
+ this._notifyConsumer();
446
+ }
447
+ /** Called by system call handler when the stream ends ($sys.End). */
448
+ onEnd(index, error) {
449
+ if (this._disposed || this._completed) return;
450
+ this._completed = true;
451
+ this._completionError = error;
452
+ this._notifyConsumer();
453
+ }
454
+ // -- IRpcObject --
455
+ reconnect() {
456
+ this._sendAck(this._nextExpectedIndex, true);
457
+ }
458
+ disconnect() {
459
+ if (this._completed) return;
460
+ this._completed = true;
461
+ this._completionError = new Error("Peer disconnected.");
462
+ this._notifyConsumer();
463
+ }
464
+ // -- AsyncIterable --
465
+ [Symbol.asyncIterator]() {
466
+ if (this._iterating) throw new Error("RpcStream can only be iterated once.");
467
+ this._iterating = true;
468
+ let bufferIndex = 0;
469
+ let consumedIndex = 0;
470
+ const self = this;
471
+ return {
472
+ async next() {
473
+ if (!self._started) {
474
+ self._started = true;
475
+ self._sendAck(0, true);
476
+ }
477
+ while (true) {
478
+ if (bufferIndex < self._buffer.length) {
479
+ const value = self._buffer[bufferIndex];
480
+ bufferIndex++;
481
+ consumedIndex++;
482
+ return { value, done: false };
483
+ }
484
+ if (self._completed) {
485
+ if (self._completionError) throw self._completionError;
486
+ return { value: void 0, done: true };
487
+ }
488
+ self._consumerWaiting = new import_core4.PromiseSource();
489
+ await self._consumerWaiting.promise;
374
490
  }
375
- call.result.resolve(args[0]);
491
+ },
492
+ async return() {
493
+ self._sendAckEnd();
494
+ self.dispose();
495
+ return { value: void 0, done: true };
376
496
  }
377
- break;
497
+ };
498
+ }
499
+ dispose() {
500
+ if (this._disposed) return;
501
+ this._disposed = true;
502
+ this._completed = true;
503
+ this.peer.remoteObjects.unregister(this);
504
+ this._notifyConsumer();
505
+ }
506
+ // -- Private --
507
+ _notifyConsumer() {
508
+ if (this._consumerWaiting !== null) {
509
+ this._consumerWaiting.resolve();
510
+ this._consumerWaiting = null;
378
511
  }
379
- case RpcSystemCalls.error: {
380
- const call = outboundTracker.remove(relatedId);
381
- if (call !== void 0) {
382
- const errorInfo = args[0];
383
- const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
384
- call.result.reject(new Error(msg));
385
- }
386
- break;
512
+ }
513
+ _maybeSendAck(nextIndex) {
514
+ if (this.ackPeriod <= 0) return;
515
+ const threshold = this._ackSentUpTo + this.ackPeriod;
516
+ if (nextIndex >= threshold) {
517
+ this._sendAck(nextIndex, false);
387
518
  }
388
- case RpcSystemCalls.cancel: {
389
- inboundTracker.remove(relatedId);
390
- break;
519
+ }
520
+ _sendAck(nextIndex, _mustReset) {
521
+ this._ackSentUpTo = nextIndex;
522
+ const conn = this.peer.connection;
523
+ if (conn) {
524
+ this.peer.hub.systemCallSender.ack(conn, this.id.localId, nextIndex, this.id.hostId);
391
525
  }
392
- case RpcSystemCalls.keepAlive: {
393
- break;
526
+ }
527
+ _sendAckEnd() {
528
+ const conn = this.peer.connection;
529
+ if (conn) {
530
+ this.peer.hub.systemCallSender.ackEnd(conn, this.id.localId, this.id.hostId);
531
+ }
532
+ }
533
+ };
534
+ function resolveStreamRefs(value, peer) {
535
+ if (value === null || value === void 0) return value;
536
+ if (typeof value === "string") {
537
+ const ref = parseStreamRef(value);
538
+ if (ref !== null) {
539
+ const stream = new RpcStream(ref, peer);
540
+ peer.remoteObjects.register(stream);
541
+ return stream;
394
542
  }
543
+ return value;
395
544
  }
545
+ if (Array.isArray(value)) {
546
+ for (let i = 0; i < value.length; i++) {
547
+ value[i] = resolveStreamRefs(value[i], peer);
548
+ }
549
+ return value;
550
+ }
551
+ if (typeof value === "object") {
552
+ for (const key of Object.keys(value)) {
553
+ value[key] = resolveStreamRefs(
554
+ value[key],
555
+ peer
556
+ );
557
+ }
558
+ return value;
559
+ }
560
+ return value;
396
561
  }
397
562
 
563
+ // src/rpc-system-call-handler.ts
564
+ var RpcSystemCallHandler = class {
565
+ handle(message, args, peer) {
566
+ const method = message.Method;
567
+ const relatedId = message.RelatedId ?? 0;
568
+ switch (method) {
569
+ case RpcSystemCalls.ok: {
570
+ const call = peer.outbound.get(relatedId);
571
+ if (call !== void 0) {
572
+ if (call.removeOnOk) {
573
+ peer.outbound.remove(relatedId);
574
+ }
575
+ call.result.resolve(args[0]);
576
+ }
577
+ break;
578
+ }
579
+ case RpcSystemCalls.error: {
580
+ const call = peer.outbound.remove(relatedId);
581
+ if (call !== void 0) {
582
+ const errorInfo = args[0];
583
+ const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
584
+ call.result.reject(new Error(msg));
585
+ }
586
+ break;
587
+ }
588
+ case RpcSystemCalls.cancel: {
589
+ peer.inbound.remove(relatedId);
590
+ break;
591
+ }
592
+ case RpcSystemCalls.keepAlive: {
593
+ break;
594
+ }
595
+ case RpcSystemCalls.item: {
596
+ const stream = peer.remoteObjects.get(relatedId);
597
+ if (stream) stream.onItem(args[0], resolveStreamRefs(args[1], peer));
598
+ break;
599
+ }
600
+ case RpcSystemCalls.batch: {
601
+ const stream = peer.remoteObjects.get(relatedId);
602
+ if (stream) {
603
+ const items = args[1];
604
+ for (let i = 0; i < items.length; i++) items[i] = resolveStreamRefs(items[i], peer);
605
+ stream.onBatch(args[0], items);
606
+ }
607
+ break;
608
+ }
609
+ case RpcSystemCalls.end: {
610
+ const stream = peer.remoteObjects.get(relatedId);
611
+ if (stream) {
612
+ const errorInfo = args[1];
613
+ const msg = errorInfo?.Message ?? errorInfo?.message;
614
+ const error = msg ? new Error(msg) : null;
615
+ stream.onEnd(args[0], error);
616
+ }
617
+ break;
618
+ }
619
+ case RpcSystemCalls.ack: {
620
+ const sender = peer.sharedObjects.get(relatedId);
621
+ sender?.onAck(args[0], args[1]);
622
+ break;
623
+ }
624
+ case RpcSystemCalls.ackEnd: {
625
+ const sender = peer.sharedObjects.get(relatedId);
626
+ sender?.onAckEnd(args[0]);
627
+ break;
628
+ }
629
+ }
630
+ }
631
+ };
632
+
398
633
  // src/rpc-system-call-sender.ts
399
634
  var RpcSystemCallSender = class {
400
635
  handshake(conn, peerId, hubId, index) {
@@ -427,17 +662,193 @@ var RpcSystemCallSender = class {
427
662
  const msg = serializeMessage({ Method: RpcSystemCalls.keepAlive }, [activeCallIds]);
428
663
  conn.send(msg);
429
664
  }
665
+ item(conn, localId, index, item) {
666
+ const msg = serializeMessage(
667
+ { Method: RpcSystemCalls.item, RelatedId: localId },
668
+ [index, item]
669
+ );
670
+ conn.send(msg);
671
+ }
672
+ batch(conn, localId, index, items) {
673
+ const msg = serializeMessage(
674
+ { Method: RpcSystemCalls.batch, RelatedId: localId },
675
+ [index, items]
676
+ );
677
+ conn.send(msg);
678
+ }
679
+ end(conn, localId, index, error = null) {
680
+ const msg = serializeMessage(
681
+ { Method: RpcSystemCalls.end, RelatedId: localId },
682
+ [index, error]
683
+ );
684
+ conn.send(msg);
685
+ }
686
+ ack(conn, localId, nextIndex, hostId) {
687
+ const msg = serializeMessage(
688
+ { Method: RpcSystemCalls.ack, RelatedId: localId },
689
+ [nextIndex, hostId]
690
+ );
691
+ conn.send(msg);
692
+ }
693
+ ackEnd(conn, localId, hostId) {
694
+ const msg = serializeMessage(
695
+ { Method: RpcSystemCalls.ackEnd, RelatedId: localId },
696
+ [hostId]
697
+ );
698
+ conn.send(msg);
699
+ }
430
700
  };
431
701
 
432
- // src/rpc-peer.ts
702
+ // src/rpc-stream-sender.ts
433
703
  var import_core5 = require("@actuallab/core");
704
+ var DEFAULT_ACK_PERIOD = 256;
705
+ var DEFAULT_ACK_ADVANCE = 128;
706
+ var RpcStreamSender = class {
707
+ id;
708
+ kind = RpcObjectKind.Local;
709
+ peer;
710
+ ackPeriod;
711
+ ackAdvance;
712
+ _nextIndex = 0;
713
+ _ended = false;
714
+ _started = new import_core5.PromiseSource();
715
+ constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE) {
716
+ const localId = peer.sharedObjects.nextId();
717
+ this.id = { hostId: peer.hub.hubId, localId };
718
+ this.peer = peer;
719
+ this.ackPeriod = ackPeriod;
720
+ this.ackAdvance = ackAdvance;
721
+ }
722
+ /** Returns the stream reference string for the $sys.Ok response. */
723
+ toRef() {
724
+ return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance}`;
725
+ }
726
+ /** Called by system call handler when $sys.Ack is received from the client. */
727
+ onAck(_nextIndex, _hostId) {
728
+ if (!this._started.isCompleted) {
729
+ this._started.resolve();
730
+ }
731
+ }
732
+ /** Called by system call handler when $sys.AckEnd is received from the client. */
733
+ onAckEnd(_hostId) {
734
+ this._ended = true;
735
+ this.peer.sharedObjects.unregister(this);
736
+ }
737
+ /** Send a single item to the client. */
738
+ sendItem(item) {
739
+ if (this._ended) return;
740
+ const conn = this.peer.connection;
741
+ if (!conn) return;
742
+ this.peer.hub.systemCallSender.item(conn, this.id.localId, this._nextIndex, item);
743
+ this._nextIndex++;
744
+ }
745
+ /** Send a batch of items to the client. */
746
+ sendBatch(items) {
747
+ if (this._ended || items.length === 0) return;
748
+ const conn = this.peer.connection;
749
+ if (!conn) return;
750
+ this.peer.hub.systemCallSender.batch(conn, this.id.localId, this._nextIndex, items);
751
+ this._nextIndex += items.length;
752
+ }
753
+ /** Signal stream completion to the client. */
754
+ sendEnd(error) {
755
+ if (this._ended) return;
756
+ this._ended = true;
757
+ const conn = this.peer.connection;
758
+ if (!conn) return;
759
+ const errorInfo = error ? { Message: error.message } : null;
760
+ this.peer.hub.systemCallSender.end(conn, this.id.localId, this._nextIndex, errorInfo);
761
+ }
762
+ /**
763
+ * Consume an AsyncIterable and send all items to the client.
764
+ * Waits for the client's initial Ack before starting to pump items.
765
+ */
766
+ async writeFrom(source) {
767
+ await this._started.promise;
768
+ try {
769
+ for await (const item of source) {
770
+ if (this._ended) return;
771
+ this.sendItem(item);
772
+ }
773
+ if (!this._ended) {
774
+ this.sendEnd();
775
+ }
776
+ } catch (e) {
777
+ if (!this._ended) {
778
+ this.sendEnd(e instanceof Error ? e : new Error(String(e)));
779
+ }
780
+ }
781
+ }
782
+ // -- IRpcObject --
783
+ reconnect() {
784
+ }
785
+ disconnect() {
786
+ this._ended = true;
787
+ if (!this._started.isCompleted) {
788
+ this._started.resolve();
789
+ }
790
+ this.peer.sharedObjects.unregister(this);
791
+ }
792
+ };
793
+
794
+ // src/rpc-remote-object-tracker.ts
795
+ var RpcRemoteObjectTracker = class {
796
+ _objects = /* @__PURE__ */ new Map();
797
+ register(obj) {
798
+ this._objects.set(obj.id.localId, obj);
799
+ }
800
+ get(localId) {
801
+ return this._objects.get(localId);
802
+ }
803
+ unregister(obj) {
804
+ this._objects.delete(obj.id.localId);
805
+ }
806
+ disconnectAll() {
807
+ for (const obj of this._objects.values()) {
808
+ obj.disconnect();
809
+ }
810
+ this._objects.clear();
811
+ }
812
+ reconnectAll() {
813
+ for (const obj of this._objects.values()) {
814
+ obj.reconnect();
815
+ }
816
+ }
817
+ };
818
+
819
+ // src/rpc-shared-object-tracker.ts
820
+ var RpcSharedObjectTracker = class {
821
+ _nextId = 1;
822
+ _objects = /* @__PURE__ */ new Map();
823
+ nextId() {
824
+ return this._nextId++;
825
+ }
826
+ register(obj) {
827
+ this._objects.set(obj.id.localId, obj);
828
+ }
829
+ get(localId) {
830
+ return this._objects.get(localId);
831
+ }
832
+ unregister(obj) {
833
+ this._objects.delete(obj.id.localId);
834
+ }
835
+ disconnectAll() {
836
+ for (const obj of this._objects.values()) {
837
+ obj.disconnect();
838
+ }
839
+ this._objects.clear();
840
+ }
841
+ };
842
+
843
+ // src/rpc-peer.ts
844
+ var import_core7 = require("@actuallab/core");
434
845
 
435
846
  // src/rpc-client-peer-reconnect-delayer.ts
436
- var import_core4 = require("@actuallab/core");
437
- var RpcClientPeerReconnectDelayer = class extends import_core4.RetryDelayer {
847
+ var import_core6 = require("@actuallab/core");
848
+ var RpcClientPeerReconnectDelayer = class extends import_core6.RetryDelayer {
438
849
  constructor() {
439
850
  super();
440
- this.delays = import_core4.RetryDelaySeq.exp(1e3, 6e4);
851
+ this.delays = import_core6.RetryDelaySeq.exp(1e3, 6e4);
441
852
  }
442
853
  };
443
854
 
@@ -456,14 +867,19 @@ var RpcPeer = class {
456
867
  _connection;
457
868
  outbound = new RpcOutboundCallTracker();
458
869
  inbound = new RpcInboundCallTracker();
459
- connected = new import_core5.EventHandlerSet();
460
- disconnected = new import_core5.EventHandlerSet();
870
+ remoteObjects = new RpcRemoteObjectTracker();
871
+ sharedObjects = new RpcSharedObjectTracker();
872
+ connected = new import_core7.EventHandlerSet();
873
+ disconnected = new import_core7.EventHandlerSet();
461
874
  _pendingSends = [];
462
875
  _keepAliveTimer;
463
876
  constructor(ref, hub) {
464
877
  this.ref = ref;
465
878
  this._hub = hub;
466
879
  }
880
+ get hub() {
881
+ return this._hub;
882
+ }
467
883
  get connection() {
468
884
  return this._connection;
469
885
  }
@@ -529,6 +945,8 @@ var RpcPeer = class {
529
945
  }
530
946
  close() {
531
947
  this._stopKeepAlive();
948
+ this.remoteObjects.disconnectAll();
949
+ this.sharedObjects.disconnectAll();
532
950
  for (const call of this._pendingSends) {
533
951
  if (!call.result.isCompleted)
534
952
  call.result.reject(new Error("Peer closed."));
@@ -562,7 +980,7 @@ var RpcPeer = class {
562
980
  return;
563
981
  }
564
982
  if (method.startsWith("$sys")) {
565
- this._hub.handleSystemCall(message, args, this.outbound, this.inbound);
983
+ this._hub.systemCallHandler.handle(message, args, this);
566
984
  return;
567
985
  }
568
986
  this._handleInbound(message, args);
@@ -582,7 +1000,12 @@ var RpcPeer = class {
582
1000
  try {
583
1001
  const context = this._connection !== void 0 ? { __rpcDispatch: true, callId: relatedId, connection: this._connection } : void 0;
584
1002
  const result = await serviceHost.dispatch(method, args, context);
585
- if (!isNoWait && this._connection !== void 0) {
1003
+ if (methodDef?.stream === true && this._connection !== void 0) {
1004
+ const sender = new RpcStreamSender(this);
1005
+ this.sharedObjects.register(sender);
1006
+ this._hub.systemCallSender.ok(this._connection, relatedId, sender.toRef());
1007
+ void sender.writeFrom(result);
1008
+ } else if (!isNoWait && this._connection !== void 0) {
586
1009
  this._hub.systemCallSender.ok(this._connection, relatedId, result);
587
1010
  }
588
1011
  } catch (e) {
@@ -635,9 +1058,9 @@ var RpcClientPeer = class extends RpcPeer {
635
1058
  serializationFormat;
636
1059
  /** Builds the WebSocket URL for this peer. Replace to customize URL construction. */
637
1060
  connectionUrlResolver = defaultConnectionUrlResolver;
638
- peerChanged = new import_core5.EventHandlerSet();
1061
+ peerChanged = new import_core7.EventHandlerSet();
639
1062
  reconnectDelayer = new RpcClientPeerReconnectDelayer();
640
- reconnectsAtChanged = new import_core5.EventHandlerSet();
1063
+ reconnectsAtChanged = new import_core7.EventHandlerSet();
641
1064
  get reconnectsAt() {
642
1065
  return this._reconnectsAt;
643
1066
  }
@@ -667,7 +1090,7 @@ var RpcClientPeer = class extends RpcPeer {
667
1090
  const ws = wsFactory?.(connUrl) ?? new WebSocket(connUrl);
668
1091
  const conn = new RpcWebSocketConnection(ws);
669
1092
  this._connectionKind = 1 /* Connecting */;
670
- this._pendingHandshake = new import_core5.PromiseSource();
1093
+ this._pendingHandshake = new import_core7.PromiseSource();
671
1094
  this.setupConnection(conn);
672
1095
  this._connection = void 0;
673
1096
  const closedRejection = new Promise((_, reject) => conn.closed.add(() => reject(new Error("Connection failed"))));
@@ -723,6 +1146,11 @@ var RpcClientPeer = class extends RpcPeer {
723
1146
  _reconnect(_isPeerChanged) {
724
1147
  if (this._connection === void 0) return;
725
1148
  const conn = this._connection;
1149
+ if (_isPeerChanged) {
1150
+ this.remoteObjects.disconnectAll();
1151
+ } else {
1152
+ this.remoteObjects.reconnectAll();
1153
+ }
726
1154
  const trackerCalls = [...this.outbound.values()];
727
1155
  for (const call of trackerCalls) {
728
1156
  if (!call.removeOnOk && call.result.isCompleted) {
@@ -805,12 +1233,10 @@ function getMethodsMeta(cls) {
805
1233
  function getServiceMeta(cls) {
806
1234
  return cls[Symbol.metadata]?.[SERVICE_META];
807
1235
  }
808
- function rpcService(serviceName, options) {
1236
+ function rpcService(serviceName) {
809
1237
  return function(_target, context) {
810
1238
  const meta = context.metadata[SERVICE_META] ??= {};
811
1239
  meta.name = serviceName;
812
- if (options?.ctOffset !== void 0)
813
- meta.ctOffset = options.ctOffset;
814
1240
  };
815
1241
  }
816
1242
  function rpcMethod(options) {
@@ -820,8 +1246,7 @@ function rpcMethod(options) {
820
1246
  methods[methodName] = {
821
1247
  ...methods[methodName],
822
1248
  argCount: target.length,
823
- stream: options?.stream ?? false,
824
- noWait: options?.noWait ?? false
1249
+ returns: options?.returns
825
1250
  };
826
1251
  return target;
827
1252
  };
@@ -833,6 +1258,7 @@ var RpcHub = class {
833
1258
  peers = /* @__PURE__ */ new Map();
834
1259
  serviceHost;
835
1260
  systemCallSender = new RpcSystemCallSender();
1261
+ systemCallHandler = new RpcSystemCallHandler();
836
1262
  constructor(hubId) {
837
1263
  this.hubId = hubId ?? crypto.randomUUID();
838
1264
  this.serviceHost = new RpcServiceHost();
@@ -906,19 +1332,14 @@ var RpcHub = class {
906
1332
  for (const peer of this.peers.values()) peer.close();
907
1333
  this.peers.clear();
908
1334
  }
909
- /** Dispatch an inbound system call. Override in FusionHub to handle compute-specific system calls. */
910
- handleSystemCall(message, args, outbound, inbound) {
911
- handleSystemCall(message, args, outbound, inbound);
912
- }
913
1335
  /** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */
914
1336
  _buildServiceDef(cls) {
915
1337
  const svcMeta = getServiceMeta(cls);
916
1338
  if (svcMeta === void 0) throw new Error("Contract class missing @rpcService metadata");
917
1339
  const methodsMeta = getMethodsMeta(cls) ?? {};
918
- const ctOffset = svcMeta.ctOffset ?? 0;
919
1340
  const methods = /* @__PURE__ */ new Map();
920
1341
  for (const [name, meta] of Object.entries(methodsMeta)) {
921
- const wireArgCount = meta.argCount + ctOffset;
1342
+ const wireArgCount = meta.argCount + 1;
922
1343
  const mapKey = `${name}:${wireArgCount}`;
923
1344
  methods.set(mapKey, {
924
1345
  name,
@@ -926,8 +1347,8 @@ var RpcHub = class {
926
1347
  argCount: meta.argCount,
927
1348
  wireArgCount,
928
1349
  callTypeId: 0,
929
- stream: meta.stream ?? false,
930
- noWait: meta.noWait ?? false
1350
+ stream: meta.returns === RpcType.stream,
1351
+ noWait: meta.returns === RpcType.noWait
931
1352
  });
932
1353
  }
933
1354
  return { name: svcMeta.name, methods };
@@ -951,11 +1372,24 @@ var RpcHub = class {
951
1372
  peer.callNoWait(wireName, callArgs);
952
1373
  };
953
1374
  }
1375
+ if (methodDef.stream) {
1376
+ return async (...args) => {
1377
+ const callArgs = args.slice(0, methodDef.argCount);
1378
+ const outboundCall = peer.call(wireName, callArgs);
1379
+ const result = await outboundCall.result.promise;
1380
+ const ref = parseStreamRef(result);
1381
+ if (!ref) throw new Error(`Expected stream reference, got: ${JSON.stringify(result)}`);
1382
+ const stream = new RpcStream(ref, peer);
1383
+ peer.remoteObjects.register(stream);
1384
+ return stream;
1385
+ };
1386
+ }
954
1387
  const callOptions = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
955
1388
  return async (...args) => {
956
1389
  const callArgs = args.slice(0, methodDef.argCount);
957
1390
  const outboundCall = peer.call(wireName, callArgs, callOptions);
958
- return outboundCall.result.promise;
1391
+ const result = await outboundCall.result.promise;
1392
+ return resolveStreamRefs(result, peer);
959
1393
  };
960
1394
  }
961
1395
  };
@@ -1041,13 +1475,13 @@ function getStateDescription(state, useLastError = false) {
1041
1475
  }
1042
1476
 
1043
1477
  // src/rpc-peer-state-monitor.ts
1044
- var import_core6 = require("@actuallab/core");
1478
+ var import_core8 = require("@actuallab/core");
1045
1479
  var JustConnectedPeriodMs = 1500;
1046
1480
  var JustDisconnectedPeriodMs = 3e3;
1047
1481
  var MinReconnectsInMs = 1e3;
1048
1482
  var RpcPeerStateMonitor = class {
1049
1483
  peer;
1050
- stateChanged = new import_core6.EventHandlerSet();
1484
+ stateChanged = new import_core8.EventHandlerSet();
1051
1485
  _rawConnected;
1052
1486
  _connectedAt = 0;
1053
1487
  _disconnectedAt = 0;
@@ -1166,14 +1600,20 @@ var RpcPeerStateMonitor = class {
1166
1600
  RpcInboundCall,
1167
1601
  RpcInboundCallTracker,
1168
1602
  RpcMessageChannelConnection,
1603
+ RpcObjectKind,
1169
1604
  RpcOutboundCall,
1170
1605
  RpcOutboundCallTracker,
1171
1606
  RpcPeer,
1172
1607
  RpcPeerConnectionKind,
1173
1608
  RpcPeerStateKind,
1174
1609
  RpcPeerStateMonitor,
1610
+ RpcRemoteObjectTracker,
1175
1611
  RpcServerPeer,
1176
1612
  RpcServiceHost,
1613
+ RpcSharedObjectTracker,
1614
+ RpcStream,
1615
+ RpcStreamSender,
1616
+ RpcSystemCallHandler,
1177
1617
  RpcSystemCallSender,
1178
1618
  RpcSystemCalls,
1179
1619
  RpcType,
@@ -1187,9 +1627,10 @@ var RpcPeerStateMonitor = class {
1187
1627
  getMethodsMeta,
1188
1628
  getServiceMeta,
1189
1629
  getStateDescription,
1190
- handleSystemCall,
1191
1630
  isConnected,
1192
1631
  likelyConnected,
1632
+ parseStreamRef,
1633
+ resolveStreamRefs,
1193
1634
  rpcMethod,
1194
1635
  rpcService,
1195
1636
  serializeFrame,