@actuallab/rpc 12.1.62 → 12.1.100

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.js CHANGED
@@ -10,7 +10,12 @@ var RpcSystemCalls = {
10
10
  error: "$sys.Error:1",
11
11
  cancel: "$sys.Cancel:0",
12
12
  keepAlive: "$sys.KeepAlive:1",
13
- reconnect: "$sys.Reconnect:3"
13
+ reconnect: "$sys.Reconnect:3",
14
+ ack: "$sys.Ack:2",
15
+ ackEnd: "$sys.AckEnd:1",
16
+ item: "$sys.I:2",
17
+ batch: "$sys.B:2",
18
+ end: "$sys.End:2"
14
19
  };
15
20
  var ENVELOPE_DELIMITER = "\n";
16
21
  var ARG_DELIMITER = "";
@@ -170,18 +175,18 @@ function createMessageChannelPair() {
170
175
  var RpcType = {
171
176
  object: /* @__PURE__ */ Symbol("object"),
172
177
  stream: /* @__PURE__ */ Symbol("stream"),
178
+ noWait: /* @__PURE__ */ Symbol("noWait"),
173
179
  void: /* @__PURE__ */ Symbol("void")
174
180
  };
175
181
  function wireMethodName(def) {
176
182
  return `${def.serviceName}.${def.name}:${def.wireArgCount}`;
177
183
  }
178
- function defineRpcService(name, methods, options) {
179
- const ctOffset = options?.ctOffset ?? 0;
184
+ function defineRpcService(name, methods) {
180
185
  const map = /* @__PURE__ */ new Map();
181
186
  for (const [key, input] of Object.entries(methods)) {
182
187
  const colonIndex = key.indexOf(":");
183
188
  const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
184
- const wireArgCount = input.args.length + ctOffset;
189
+ const wireArgCount = input.wireArgCount ?? input.args.length + 1;
185
190
  const mapKey = `${cleanName}:${wireArgCount}`;
186
191
  map.set(mapKey, {
187
192
  name: cleanName,
@@ -189,8 +194,8 @@ function defineRpcService(name, methods, options) {
189
194
  argCount: input.args.length,
190
195
  wireArgCount,
191
196
  callTypeId: input.callTypeId ?? 0,
192
- stream: input.stream ?? input.returns === RpcType.stream,
193
- noWait: input.noWait ?? false
197
+ stream: input.returns === RpcType.stream,
198
+ noWait: input.returns === RpcType.noWait
194
199
  });
195
200
  }
196
201
  return { name, methods: map };
@@ -295,40 +300,270 @@ var RpcInboundCallTracker = class {
295
300
  }
296
301
  };
297
302
 
298
- // src/rpc-system-call-handler.ts
299
- function handleSystemCall(message, args, outboundTracker, inboundTracker) {
300
- const method = message.Method;
301
- const relatedId = message.RelatedId ?? 0;
302
- switch (method) {
303
- case RpcSystemCalls.ok: {
304
- const call = outboundTracker.get(relatedId);
305
- if (call !== void 0) {
306
- if (call.removeOnOk) {
307
- outboundTracker.remove(relatedId);
303
+ // src/rpc-stream.ts
304
+ import { PromiseSource as PromiseSource3 } from "@actuallab/core";
305
+
306
+ // src/rpc-object.ts
307
+ var RpcObjectKind = { Local: 0, Remote: 1 };
308
+
309
+ // src/rpc-stream.ts
310
+ function parseStreamRef(value) {
311
+ if (typeof value !== "string") return null;
312
+ const parts = value.split(",");
313
+ if (parts.length < 4 || parts.length > 5) return null;
314
+ const hostId = parts[0];
315
+ const localId = parseInt(parts[1], 10);
316
+ const ackPeriod = parseInt(parts[2], 10);
317
+ const ackAdvance = parseInt(parts[3], 10);
318
+ if (isNaN(localId) || isNaN(ackPeriod) || isNaN(ackAdvance)) return null;
319
+ const allowReconnect = parts.length < 5 || parts[4] !== "0";
320
+ return { hostId, localId, ackPeriod, ackAdvance, allowReconnect };
321
+ }
322
+ var RpcStream = class {
323
+ id;
324
+ kind = RpcObjectKind.Remote;
325
+ allowReconnect;
326
+ peer;
327
+ ackPeriod;
328
+ ackAdvance;
329
+ _buffer = [];
330
+ _nextExpectedIndex = 0;
331
+ _consumerWaiting = null;
332
+ _completed = false;
333
+ _completionError = null;
334
+ _disposed = false;
335
+ _started = false;
336
+ _iterating = false;
337
+ _ackSentUpTo = 0;
338
+ constructor(ref, peer) {
339
+ this.id = { hostId: ref.hostId, localId: ref.localId };
340
+ this.allowReconnect = ref.allowReconnect;
341
+ this.peer = peer;
342
+ this.ackPeriod = ref.ackPeriod;
343
+ this.ackAdvance = ref.ackAdvance;
344
+ }
345
+ /** Called by system call handler when a single item arrives ($sys.I). */
346
+ onItem(index, item) {
347
+ if (this._disposed || this._completed) return;
348
+ if (index > this._nextExpectedIndex) {
349
+ this._sendAck(this._nextExpectedIndex, true);
350
+ return;
351
+ }
352
+ if (index < this._nextExpectedIndex) {
353
+ this._maybeSendAck(index + 1);
354
+ return;
355
+ }
356
+ this._buffer.push(item);
357
+ this._nextExpectedIndex = index + 1;
358
+ this._maybeSendAck(this._nextExpectedIndex);
359
+ this._notifyConsumer();
360
+ }
361
+ /** Called by system call handler when a batch arrives ($sys.B). */
362
+ onBatch(index, items) {
363
+ if (this._disposed || this._completed) return;
364
+ if (index > this._nextExpectedIndex) {
365
+ this._sendAck(this._nextExpectedIndex, true);
366
+ return;
367
+ }
368
+ if (index < this._nextExpectedIndex) {
369
+ this._maybeSendAck(index + items.length);
370
+ return;
371
+ }
372
+ for (const item of items) this._buffer.push(item);
373
+ this._nextExpectedIndex = index + items.length;
374
+ this._maybeSendAck(this._nextExpectedIndex);
375
+ this._notifyConsumer();
376
+ }
377
+ /** Called by system call handler when the stream ends ($sys.End). */
378
+ onEnd(index, error) {
379
+ if (this._disposed || this._completed) return;
380
+ this._completed = true;
381
+ this._completionError = error;
382
+ this._notifyConsumer();
383
+ }
384
+ // -- IRpcObject --
385
+ reconnect() {
386
+ if (this.allowReconnect) {
387
+ this._sendAck(this._nextExpectedIndex, true);
388
+ } else {
389
+ this.disconnect();
390
+ }
391
+ }
392
+ disconnect() {
393
+ if (this._completed) return;
394
+ this._completed = true;
395
+ this._completionError = new Error("Peer disconnected.");
396
+ this._notifyConsumer();
397
+ }
398
+ // -- AsyncIterable --
399
+ [Symbol.asyncIterator]() {
400
+ if (this._iterating) throw new Error("RpcStream can only be iterated once.");
401
+ this._iterating = true;
402
+ let bufferIndex = 0;
403
+ let consumedIndex = 0;
404
+ const self = this;
405
+ return {
406
+ async next() {
407
+ if (!self._started) {
408
+ self._started = true;
409
+ self._sendAck(0, true);
410
+ }
411
+ while (true) {
412
+ if (bufferIndex < self._buffer.length) {
413
+ const value = self._buffer[bufferIndex];
414
+ bufferIndex++;
415
+ consumedIndex++;
416
+ return { value, done: false };
417
+ }
418
+ if (self._completed) {
419
+ if (self._completionError) throw self._completionError;
420
+ return { value: void 0, done: true };
421
+ }
422
+ self._consumerWaiting = new PromiseSource3();
423
+ await self._consumerWaiting.promise;
308
424
  }
309
- call.result.resolve(args[0]);
425
+ },
426
+ async return() {
427
+ self._sendAckEnd();
428
+ self.dispose();
429
+ return { value: void 0, done: true };
310
430
  }
311
- break;
431
+ };
432
+ }
433
+ dispose() {
434
+ if (this._disposed) return;
435
+ this._disposed = true;
436
+ this._completed = true;
437
+ this.peer.remoteObjects.unregister(this);
438
+ this._notifyConsumer();
439
+ }
440
+ // -- Private --
441
+ _notifyConsumer() {
442
+ if (this._consumerWaiting !== null) {
443
+ this._consumerWaiting.resolve();
444
+ this._consumerWaiting = null;
312
445
  }
313
- case RpcSystemCalls.error: {
314
- const call = outboundTracker.remove(relatedId);
315
- if (call !== void 0) {
316
- const errorInfo = args[0];
317
- const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
318
- call.result.reject(new Error(msg));
319
- }
320
- break;
446
+ }
447
+ _maybeSendAck(nextIndex) {
448
+ if (this.ackPeriod <= 0) return;
449
+ const threshold = this._ackSentUpTo + this.ackPeriod;
450
+ if (nextIndex >= threshold) {
451
+ this._sendAck(nextIndex, false);
452
+ }
453
+ }
454
+ _sendAck(nextIndex, _mustReset) {
455
+ this._ackSentUpTo = nextIndex;
456
+ const conn = this.peer.connection;
457
+ if (conn) {
458
+ this.peer.hub.systemCallSender.ack(conn, this.id.localId, nextIndex, this.id.hostId);
459
+ }
460
+ }
461
+ _sendAckEnd() {
462
+ const conn = this.peer.connection;
463
+ if (conn) {
464
+ this.peer.hub.systemCallSender.ackEnd(conn, this.id.localId, this.id.hostId);
465
+ }
466
+ }
467
+ };
468
+ function resolveStreamRefs(value, peer) {
469
+ if (value === null || value === void 0) return value;
470
+ if (typeof value === "string") {
471
+ const ref = parseStreamRef(value);
472
+ if (ref !== null) {
473
+ const stream = new RpcStream(ref, peer);
474
+ peer.remoteObjects.register(stream);
475
+ return stream;
321
476
  }
322
- case RpcSystemCalls.cancel: {
323
- inboundTracker.remove(relatedId);
324
- break;
477
+ return value;
478
+ }
479
+ if (Array.isArray(value)) {
480
+ for (let i = 0; i < value.length; i++) {
481
+ value[i] = resolveStreamRefs(value[i], peer);
325
482
  }
326
- case RpcSystemCalls.keepAlive: {
327
- break;
483
+ return value;
484
+ }
485
+ if (typeof value === "object") {
486
+ for (const key of Object.keys(value)) {
487
+ value[key] = resolveStreamRefs(
488
+ value[key],
489
+ peer
490
+ );
328
491
  }
492
+ return value;
329
493
  }
494
+ return value;
330
495
  }
331
496
 
497
+ // src/rpc-system-call-handler.ts
498
+ var RpcSystemCallHandler = class {
499
+ handle(message, args, peer) {
500
+ const method = message.Method;
501
+ const relatedId = message.RelatedId ?? 0;
502
+ switch (method) {
503
+ case RpcSystemCalls.ok: {
504
+ const call = peer.outbound.get(relatedId);
505
+ if (call !== void 0) {
506
+ if (call.removeOnOk) {
507
+ peer.outbound.remove(relatedId);
508
+ }
509
+ call.result.resolve(args[0]);
510
+ }
511
+ break;
512
+ }
513
+ case RpcSystemCalls.error: {
514
+ const call = peer.outbound.remove(relatedId);
515
+ if (call !== void 0) {
516
+ const errorInfo = args[0];
517
+ const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
518
+ call.result.reject(new Error(msg));
519
+ }
520
+ break;
521
+ }
522
+ case RpcSystemCalls.cancel: {
523
+ peer.inbound.remove(relatedId);
524
+ break;
525
+ }
526
+ case RpcSystemCalls.keepAlive: {
527
+ break;
528
+ }
529
+ case RpcSystemCalls.item: {
530
+ const stream = peer.remoteObjects.get(relatedId);
531
+ if (stream) stream.onItem(args[0], resolveStreamRefs(args[1], peer));
532
+ break;
533
+ }
534
+ case RpcSystemCalls.batch: {
535
+ const stream = peer.remoteObjects.get(relatedId);
536
+ if (stream) {
537
+ const items = args[1];
538
+ for (let i = 0; i < items.length; i++) items[i] = resolveStreamRefs(items[i], peer);
539
+ stream.onBatch(args[0], items);
540
+ }
541
+ break;
542
+ }
543
+ case RpcSystemCalls.end: {
544
+ const stream = peer.remoteObjects.get(relatedId);
545
+ if (stream) {
546
+ const errorInfo = args[1];
547
+ const msg = errorInfo?.Message ?? errorInfo?.message;
548
+ const error = msg ? new Error(msg) : null;
549
+ stream.onEnd(args[0], error);
550
+ }
551
+ break;
552
+ }
553
+ case RpcSystemCalls.ack: {
554
+ const sender = peer.sharedObjects.get(relatedId);
555
+ sender?.onAck(args[0], args[1]);
556
+ break;
557
+ }
558
+ case RpcSystemCalls.ackEnd: {
559
+ const sender = peer.sharedObjects.get(relatedId);
560
+ sender?.onAckEnd(args[0]);
561
+ break;
562
+ }
563
+ }
564
+ }
565
+ };
566
+
332
567
  // src/rpc-system-call-sender.ts
333
568
  var RpcSystemCallSender = class {
334
569
  handshake(conn, peerId, hubId, index) {
@@ -361,10 +596,191 @@ var RpcSystemCallSender = class {
361
596
  const msg = serializeMessage({ Method: RpcSystemCalls.keepAlive }, [activeCallIds]);
362
597
  conn.send(msg);
363
598
  }
599
+ item(conn, localId, index, item) {
600
+ const msg = serializeMessage(
601
+ { Method: RpcSystemCalls.item, RelatedId: localId },
602
+ [index, item]
603
+ );
604
+ conn.send(msg);
605
+ }
606
+ batch(conn, localId, index, items) {
607
+ const msg = serializeMessage(
608
+ { Method: RpcSystemCalls.batch, RelatedId: localId },
609
+ [index, items]
610
+ );
611
+ conn.send(msg);
612
+ }
613
+ end(conn, localId, index, error = null) {
614
+ const msg = serializeMessage(
615
+ { Method: RpcSystemCalls.end, RelatedId: localId },
616
+ [index, error]
617
+ );
618
+ conn.send(msg);
619
+ }
620
+ ack(conn, localId, nextIndex, hostId) {
621
+ const msg = serializeMessage(
622
+ { Method: RpcSystemCalls.ack, RelatedId: localId },
623
+ [nextIndex, hostId]
624
+ );
625
+ conn.send(msg);
626
+ }
627
+ ackEnd(conn, localId, hostId) {
628
+ const msg = serializeMessage(
629
+ { Method: RpcSystemCalls.ackEnd, RelatedId: localId },
630
+ [hostId]
631
+ );
632
+ conn.send(msg);
633
+ }
634
+ };
635
+
636
+ // src/rpc-stream-sender.ts
637
+ import { PromiseSource as PromiseSource4 } from "@actuallab/core";
638
+ var DEFAULT_ACK_PERIOD = 256;
639
+ var DEFAULT_ACK_ADVANCE = 128;
640
+ var RpcStreamSender = class {
641
+ id;
642
+ kind = RpcObjectKind.Local;
643
+ allowReconnect;
644
+ peer;
645
+ ackPeriod;
646
+ ackAdvance;
647
+ _nextIndex = 0;
648
+ _ended = false;
649
+ _started = new PromiseSource4();
650
+ constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true) {
651
+ const localId = peer.sharedObjects.nextId();
652
+ this.id = { hostId: peer.hub.hubId, localId };
653
+ this.allowReconnect = allowReconnect;
654
+ this.peer = peer;
655
+ this.ackPeriod = ackPeriod;
656
+ this.ackAdvance = ackAdvance;
657
+ }
658
+ /** Returns the stream reference string for the $sys.Ok response. */
659
+ toRef() {
660
+ return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"}`;
661
+ }
662
+ /** Called by system call handler when $sys.Ack is received from the client. */
663
+ onAck(_nextIndex, _hostId) {
664
+ if (!this._started.isCompleted) {
665
+ this._started.resolve();
666
+ }
667
+ }
668
+ /** Called by system call handler when $sys.AckEnd is received from the client. */
669
+ onAckEnd(_hostId) {
670
+ this._ended = true;
671
+ this.peer.sharedObjects.unregister(this);
672
+ }
673
+ /** Send a single item to the client. */
674
+ sendItem(item) {
675
+ if (this._ended) return;
676
+ const conn = this.peer.connection;
677
+ if (!conn) return;
678
+ this.peer.hub.systemCallSender.item(conn, this.id.localId, this._nextIndex, item);
679
+ this._nextIndex++;
680
+ }
681
+ /** Send a batch of items to the client. */
682
+ sendBatch(items) {
683
+ if (this._ended || items.length === 0) return;
684
+ const conn = this.peer.connection;
685
+ if (!conn) return;
686
+ this.peer.hub.systemCallSender.batch(conn, this.id.localId, this._nextIndex, items);
687
+ this._nextIndex += items.length;
688
+ }
689
+ /** Signal stream completion to the client. */
690
+ sendEnd(error) {
691
+ if (this._ended) return;
692
+ this._ended = true;
693
+ const conn = this.peer.connection;
694
+ if (!conn) return;
695
+ const errorInfo = error ? { Message: error.message } : null;
696
+ this.peer.hub.systemCallSender.end(conn, this.id.localId, this._nextIndex, errorInfo);
697
+ }
698
+ /**
699
+ * Consume an AsyncIterable and send all items to the client.
700
+ * Waits for the client's initial Ack before starting to pump items.
701
+ */
702
+ async writeFrom(source) {
703
+ await this._started.promise;
704
+ try {
705
+ for await (const item of source) {
706
+ if (this._ended) return;
707
+ this.sendItem(item);
708
+ }
709
+ if (!this._ended) {
710
+ this.sendEnd();
711
+ }
712
+ } catch (e) {
713
+ if (!this._ended) {
714
+ this.sendEnd(e instanceof Error ? e : new Error(String(e)));
715
+ }
716
+ }
717
+ }
718
+ // -- IRpcObject --
719
+ reconnect() {
720
+ }
721
+ disconnect() {
722
+ this._ended = true;
723
+ if (!this._started.isCompleted) {
724
+ this._started.resolve();
725
+ }
726
+ this.peer.sharedObjects.unregister(this);
727
+ }
728
+ };
729
+
730
+ // src/rpc-remote-object-tracker.ts
731
+ var RpcRemoteObjectTracker = class {
732
+ _objects = /* @__PURE__ */ new Map();
733
+ register(obj) {
734
+ this._objects.set(obj.id.localId, obj);
735
+ }
736
+ get(localId) {
737
+ return this._objects.get(localId);
738
+ }
739
+ unregister(obj) {
740
+ this._objects.delete(obj.id.localId);
741
+ }
742
+ disconnectAll() {
743
+ for (const obj of this._objects.values()) {
744
+ obj.disconnect();
745
+ }
746
+ this._objects.clear();
747
+ }
748
+ reconnectAll() {
749
+ for (const obj of this._objects.values()) {
750
+ if (obj.allowReconnect)
751
+ obj.reconnect();
752
+ else
753
+ obj.disconnect();
754
+ }
755
+ }
756
+ };
757
+
758
+ // src/rpc-shared-object-tracker.ts
759
+ var RpcSharedObjectTracker = class {
760
+ _nextId = 1;
761
+ _objects = /* @__PURE__ */ new Map();
762
+ nextId() {
763
+ return this._nextId++;
764
+ }
765
+ register(obj) {
766
+ this._objects.set(obj.id.localId, obj);
767
+ }
768
+ get(localId) {
769
+ return this._objects.get(localId);
770
+ }
771
+ unregister(obj) {
772
+ this._objects.delete(obj.id.localId);
773
+ }
774
+ disconnectAll() {
775
+ for (const obj of this._objects.values()) {
776
+ obj.disconnect();
777
+ }
778
+ this._objects.clear();
779
+ }
364
780
  };
365
781
 
366
782
  // src/rpc-peer.ts
367
- import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource3 } from "@actuallab/core";
783
+ import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource5 } from "@actuallab/core";
368
784
 
369
785
  // src/rpc-client-peer-reconnect-delayer.ts
370
786
  import { RetryDelaySeq, RetryDelayer } from "@actuallab/core";
@@ -390,6 +806,8 @@ var RpcPeer = class {
390
806
  _connection;
391
807
  outbound = new RpcOutboundCallTracker();
392
808
  inbound = new RpcInboundCallTracker();
809
+ remoteObjects = new RpcRemoteObjectTracker();
810
+ sharedObjects = new RpcSharedObjectTracker();
393
811
  connected = new EventHandlerSet3();
394
812
  disconnected = new EventHandlerSet3();
395
813
  _pendingSends = [];
@@ -398,6 +816,9 @@ var RpcPeer = class {
398
816
  this.ref = ref;
399
817
  this._hub = hub;
400
818
  }
819
+ get hub() {
820
+ return this._hub;
821
+ }
401
822
  get connection() {
402
823
  return this._connection;
403
824
  }
@@ -463,6 +884,8 @@ var RpcPeer = class {
463
884
  }
464
885
  close() {
465
886
  this._stopKeepAlive();
887
+ this.remoteObjects.disconnectAll();
888
+ this.sharedObjects.disconnectAll();
466
889
  for (const call of this._pendingSends) {
467
890
  if (!call.result.isCompleted)
468
891
  call.result.reject(new Error("Peer closed."));
@@ -496,7 +919,7 @@ var RpcPeer = class {
496
919
  return;
497
920
  }
498
921
  if (method.startsWith("$sys")) {
499
- this._hub.handleSystemCall(message, args, this.outbound, this.inbound);
922
+ this._hub.systemCallHandler.handle(message, args, this);
500
923
  return;
501
924
  }
502
925
  this._handleInbound(message, args);
@@ -516,7 +939,12 @@ var RpcPeer = class {
516
939
  try {
517
940
  const context = this._connection !== void 0 ? { __rpcDispatch: true, callId: relatedId, connection: this._connection } : void 0;
518
941
  const result = await serviceHost.dispatch(method, args, context);
519
- if (!isNoWait && this._connection !== void 0) {
942
+ if (methodDef?.stream === true && this._connection !== void 0) {
943
+ const sender = new RpcStreamSender(this);
944
+ this.sharedObjects.register(sender);
945
+ this._hub.systemCallSender.ok(this._connection, relatedId, sender.toRef());
946
+ void sender.writeFrom(result);
947
+ } else if (!isNoWait && this._connection !== void 0) {
520
948
  this._hub.systemCallSender.ok(this._connection, relatedId, result);
521
949
  }
522
950
  } catch (e) {
@@ -601,7 +1029,7 @@ var RpcClientPeer = class extends RpcPeer {
601
1029
  const ws = wsFactory?.(connUrl) ?? new WebSocket(connUrl);
602
1030
  const conn = new RpcWebSocketConnection(ws);
603
1031
  this._connectionKind = 1 /* Connecting */;
604
- this._pendingHandshake = new PromiseSource3();
1032
+ this._pendingHandshake = new PromiseSource5();
605
1033
  this.setupConnection(conn);
606
1034
  this._connection = void 0;
607
1035
  const closedRejection = new Promise((_, reject) => conn.closed.add(() => reject(new Error("Connection failed"))));
@@ -657,6 +1085,11 @@ var RpcClientPeer = class extends RpcPeer {
657
1085
  _reconnect(_isPeerChanged) {
658
1086
  if (this._connection === void 0) return;
659
1087
  const conn = this._connection;
1088
+ if (_isPeerChanged) {
1089
+ this.remoteObjects.disconnectAll();
1090
+ } else {
1091
+ this.remoteObjects.reconnectAll();
1092
+ }
660
1093
  const trackerCalls = [...this.outbound.values()];
661
1094
  for (const call of trackerCalls) {
662
1095
  if (!call.removeOnOk && call.result.isCompleted) {
@@ -739,12 +1172,10 @@ function getMethodsMeta(cls) {
739
1172
  function getServiceMeta(cls) {
740
1173
  return cls[Symbol.metadata]?.[SERVICE_META];
741
1174
  }
742
- function rpcService(serviceName, options) {
1175
+ function rpcService(serviceName) {
743
1176
  return function(_target, context) {
744
1177
  const meta = context.metadata[SERVICE_META] ??= {};
745
1178
  meta.name = serviceName;
746
- if (options?.ctOffset !== void 0)
747
- meta.ctOffset = options.ctOffset;
748
1179
  };
749
1180
  }
750
1181
  function rpcMethod(options) {
@@ -754,8 +1185,7 @@ function rpcMethod(options) {
754
1185
  methods[methodName] = {
755
1186
  ...methods[methodName],
756
1187
  argCount: target.length,
757
- stream: options?.stream ?? false,
758
- noWait: options?.noWait ?? false
1188
+ returns: options?.returns
759
1189
  };
760
1190
  return target;
761
1191
  };
@@ -767,6 +1197,7 @@ var RpcHub = class {
767
1197
  peers = /* @__PURE__ */ new Map();
768
1198
  serviceHost;
769
1199
  systemCallSender = new RpcSystemCallSender();
1200
+ systemCallHandler = new RpcSystemCallHandler();
770
1201
  constructor(hubId) {
771
1202
  this.hubId = hubId ?? crypto.randomUUID();
772
1203
  this.serviceHost = new RpcServiceHost();
@@ -840,19 +1271,14 @@ var RpcHub = class {
840
1271
  for (const peer of this.peers.values()) peer.close();
841
1272
  this.peers.clear();
842
1273
  }
843
- /** Dispatch an inbound system call. Override in FusionHub to handle compute-specific system calls. */
844
- handleSystemCall(message, args, outbound, inbound) {
845
- handleSystemCall(message, args, outbound, inbound);
846
- }
847
1274
  /** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */
848
1275
  _buildServiceDef(cls) {
849
1276
  const svcMeta = getServiceMeta(cls);
850
1277
  if (svcMeta === void 0) throw new Error("Contract class missing @rpcService metadata");
851
1278
  const methodsMeta = getMethodsMeta(cls) ?? {};
852
- const ctOffset = svcMeta.ctOffset ?? 0;
853
1279
  const methods = /* @__PURE__ */ new Map();
854
1280
  for (const [name, meta] of Object.entries(methodsMeta)) {
855
- const wireArgCount = meta.argCount + ctOffset;
1281
+ const wireArgCount = meta.argCount + 1;
856
1282
  const mapKey = `${name}:${wireArgCount}`;
857
1283
  methods.set(mapKey, {
858
1284
  name,
@@ -860,8 +1286,8 @@ var RpcHub = class {
860
1286
  argCount: meta.argCount,
861
1287
  wireArgCount,
862
1288
  callTypeId: 0,
863
- stream: meta.stream ?? false,
864
- noWait: meta.noWait ?? false
1289
+ stream: meta.returns === RpcType.stream,
1290
+ noWait: meta.returns === RpcType.noWait
865
1291
  });
866
1292
  }
867
1293
  return { name: svcMeta.name, methods };
@@ -885,11 +1311,24 @@ var RpcHub = class {
885
1311
  peer.callNoWait(wireName, callArgs);
886
1312
  };
887
1313
  }
1314
+ if (methodDef.stream) {
1315
+ return async (...args) => {
1316
+ const callArgs = args.slice(0, methodDef.argCount);
1317
+ const outboundCall = peer.call(wireName, callArgs);
1318
+ const result = await outboundCall.result.promise;
1319
+ const ref = parseStreamRef(result);
1320
+ if (!ref) throw new Error(`Expected stream reference, got: ${JSON.stringify(result)}`);
1321
+ const stream = new RpcStream(ref, peer);
1322
+ peer.remoteObjects.register(stream);
1323
+ return stream;
1324
+ };
1325
+ }
888
1326
  const callOptions = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
889
1327
  return async (...args) => {
890
1328
  const callArgs = args.slice(0, methodDef.argCount);
891
1329
  const outboundCall = peer.call(wireName, callArgs, callOptions);
892
- return outboundCall.result.promise;
1330
+ const result = await outboundCall.result.promise;
1331
+ return resolveStreamRefs(result, peer);
893
1332
  };
894
1333
  }
895
1334
  };
@@ -1099,14 +1538,20 @@ export {
1099
1538
  RpcInboundCall,
1100
1539
  RpcInboundCallTracker,
1101
1540
  RpcMessageChannelConnection,
1541
+ RpcObjectKind,
1102
1542
  RpcOutboundCall,
1103
1543
  RpcOutboundCallTracker,
1104
1544
  RpcPeer,
1105
1545
  RpcPeerConnectionKind,
1106
1546
  RpcPeerStateKind,
1107
1547
  RpcPeerStateMonitor,
1548
+ RpcRemoteObjectTracker,
1108
1549
  RpcServerPeer,
1109
1550
  RpcServiceHost,
1551
+ RpcSharedObjectTracker,
1552
+ RpcStream,
1553
+ RpcStreamSender,
1554
+ RpcSystemCallHandler,
1110
1555
  RpcSystemCallSender,
1111
1556
  RpcSystemCalls,
1112
1557
  RpcType,
@@ -1120,9 +1565,10 @@ export {
1120
1565
  getMethodsMeta,
1121
1566
  getServiceMeta,
1122
1567
  getStateDescription,
1123
- handleSystemCall,
1124
1568
  isConnected,
1125
1569
  likelyConnected,
1570
+ parseStreamRef,
1571
+ resolveStreamRefs,
1126
1572
  rpcMethod,
1127
1573
  rpcService,
1128
1574
  serializeFrame,