@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.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,263 @@ 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) 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
+ return { hostId, localId, ackPeriod, ackAdvance };
320
+ }
321
+ var RpcStream = class {
322
+ id;
323
+ kind = RpcObjectKind.Remote;
324
+ peer;
325
+ ackPeriod;
326
+ ackAdvance;
327
+ _buffer = [];
328
+ _nextExpectedIndex = 0;
329
+ _consumerWaiting = null;
330
+ _completed = false;
331
+ _completionError = null;
332
+ _disposed = false;
333
+ _started = false;
334
+ _iterating = false;
335
+ _ackSentUpTo = 0;
336
+ constructor(ref, peer) {
337
+ this.id = { hostId: ref.hostId, localId: ref.localId };
338
+ this.peer = peer;
339
+ this.ackPeriod = ref.ackPeriod;
340
+ this.ackAdvance = ref.ackAdvance;
341
+ }
342
+ /** Called by system call handler when a single item arrives ($sys.I). */
343
+ onItem(index, item) {
344
+ if (this._disposed || this._completed) return;
345
+ if (index > this._nextExpectedIndex) {
346
+ this._sendAck(this._nextExpectedIndex, true);
347
+ return;
348
+ }
349
+ if (index < this._nextExpectedIndex) {
350
+ this._maybeSendAck(index + 1);
351
+ return;
352
+ }
353
+ this._buffer.push(item);
354
+ this._nextExpectedIndex = index + 1;
355
+ this._maybeSendAck(this._nextExpectedIndex);
356
+ this._notifyConsumer();
357
+ }
358
+ /** Called by system call handler when a batch arrives ($sys.B). */
359
+ onBatch(index, items) {
360
+ if (this._disposed || this._completed) return;
361
+ if (index > this._nextExpectedIndex) {
362
+ this._sendAck(this._nextExpectedIndex, true);
363
+ return;
364
+ }
365
+ if (index < this._nextExpectedIndex) {
366
+ this._maybeSendAck(index + items.length);
367
+ return;
368
+ }
369
+ for (const item of items) this._buffer.push(item);
370
+ this._nextExpectedIndex = index + items.length;
371
+ this._maybeSendAck(this._nextExpectedIndex);
372
+ this._notifyConsumer();
373
+ }
374
+ /** Called by system call handler when the stream ends ($sys.End). */
375
+ onEnd(index, error) {
376
+ if (this._disposed || this._completed) return;
377
+ this._completed = true;
378
+ this._completionError = error;
379
+ this._notifyConsumer();
380
+ }
381
+ // -- IRpcObject --
382
+ reconnect() {
383
+ this._sendAck(this._nextExpectedIndex, true);
384
+ }
385
+ disconnect() {
386
+ if (this._completed) return;
387
+ this._completed = true;
388
+ this._completionError = new Error("Peer disconnected.");
389
+ this._notifyConsumer();
390
+ }
391
+ // -- AsyncIterable --
392
+ [Symbol.asyncIterator]() {
393
+ if (this._iterating) throw new Error("RpcStream can only be iterated once.");
394
+ this._iterating = true;
395
+ let bufferIndex = 0;
396
+ let consumedIndex = 0;
397
+ const self = this;
398
+ return {
399
+ async next() {
400
+ if (!self._started) {
401
+ self._started = true;
402
+ self._sendAck(0, true);
308
403
  }
309
- call.result.resolve(args[0]);
404
+ while (true) {
405
+ if (bufferIndex < self._buffer.length) {
406
+ const value = self._buffer[bufferIndex];
407
+ bufferIndex++;
408
+ consumedIndex++;
409
+ return { value, done: false };
410
+ }
411
+ if (self._completed) {
412
+ if (self._completionError) throw self._completionError;
413
+ return { value: void 0, done: true };
414
+ }
415
+ self._consumerWaiting = new PromiseSource3();
416
+ await self._consumerWaiting.promise;
417
+ }
418
+ },
419
+ async return() {
420
+ self._sendAckEnd();
421
+ self.dispose();
422
+ return { value: void 0, done: true };
310
423
  }
311
- break;
424
+ };
425
+ }
426
+ dispose() {
427
+ if (this._disposed) return;
428
+ this._disposed = true;
429
+ this._completed = true;
430
+ this.peer.remoteObjects.unregister(this);
431
+ this._notifyConsumer();
432
+ }
433
+ // -- Private --
434
+ _notifyConsumer() {
435
+ if (this._consumerWaiting !== null) {
436
+ this._consumerWaiting.resolve();
437
+ this._consumerWaiting = null;
312
438
  }
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;
439
+ }
440
+ _maybeSendAck(nextIndex) {
441
+ if (this.ackPeriod <= 0) return;
442
+ const threshold = this._ackSentUpTo + this.ackPeriod;
443
+ if (nextIndex >= threshold) {
444
+ this._sendAck(nextIndex, false);
445
+ }
446
+ }
447
+ _sendAck(nextIndex, _mustReset) {
448
+ this._ackSentUpTo = nextIndex;
449
+ const conn = this.peer.connection;
450
+ if (conn) {
451
+ this.peer.hub.systemCallSender.ack(conn, this.id.localId, nextIndex, this.id.hostId);
452
+ }
453
+ }
454
+ _sendAckEnd() {
455
+ const conn = this.peer.connection;
456
+ if (conn) {
457
+ this.peer.hub.systemCallSender.ackEnd(conn, this.id.localId, this.id.hostId);
458
+ }
459
+ }
460
+ };
461
+ function resolveStreamRefs(value, peer) {
462
+ if (value === null || value === void 0) return value;
463
+ if (typeof value === "string") {
464
+ const ref = parseStreamRef(value);
465
+ if (ref !== null) {
466
+ const stream = new RpcStream(ref, peer);
467
+ peer.remoteObjects.register(stream);
468
+ return stream;
321
469
  }
322
- case RpcSystemCalls.cancel: {
323
- inboundTracker.remove(relatedId);
324
- break;
470
+ return value;
471
+ }
472
+ if (Array.isArray(value)) {
473
+ for (let i = 0; i < value.length; i++) {
474
+ value[i] = resolveStreamRefs(value[i], peer);
325
475
  }
326
- case RpcSystemCalls.keepAlive: {
327
- break;
476
+ return value;
477
+ }
478
+ if (typeof value === "object") {
479
+ for (const key of Object.keys(value)) {
480
+ value[key] = resolveStreamRefs(
481
+ value[key],
482
+ peer
483
+ );
328
484
  }
485
+ return value;
329
486
  }
487
+ return value;
330
488
  }
331
489
 
490
+ // src/rpc-system-call-handler.ts
491
+ var RpcSystemCallHandler = class {
492
+ handle(message, args, peer) {
493
+ const method = message.Method;
494
+ const relatedId = message.RelatedId ?? 0;
495
+ switch (method) {
496
+ case RpcSystemCalls.ok: {
497
+ const call = peer.outbound.get(relatedId);
498
+ if (call !== void 0) {
499
+ if (call.removeOnOk) {
500
+ peer.outbound.remove(relatedId);
501
+ }
502
+ call.result.resolve(args[0]);
503
+ }
504
+ break;
505
+ }
506
+ case RpcSystemCalls.error: {
507
+ const call = peer.outbound.remove(relatedId);
508
+ if (call !== void 0) {
509
+ const errorInfo = args[0];
510
+ const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
511
+ call.result.reject(new Error(msg));
512
+ }
513
+ break;
514
+ }
515
+ case RpcSystemCalls.cancel: {
516
+ peer.inbound.remove(relatedId);
517
+ break;
518
+ }
519
+ case RpcSystemCalls.keepAlive: {
520
+ break;
521
+ }
522
+ case RpcSystemCalls.item: {
523
+ const stream = peer.remoteObjects.get(relatedId);
524
+ if (stream) stream.onItem(args[0], resolveStreamRefs(args[1], peer));
525
+ break;
526
+ }
527
+ case RpcSystemCalls.batch: {
528
+ const stream = peer.remoteObjects.get(relatedId);
529
+ if (stream) {
530
+ const items = args[1];
531
+ for (let i = 0; i < items.length; i++) items[i] = resolveStreamRefs(items[i], peer);
532
+ stream.onBatch(args[0], items);
533
+ }
534
+ break;
535
+ }
536
+ case RpcSystemCalls.end: {
537
+ const stream = peer.remoteObjects.get(relatedId);
538
+ if (stream) {
539
+ const errorInfo = args[1];
540
+ const msg = errorInfo?.Message ?? errorInfo?.message;
541
+ const error = msg ? new Error(msg) : null;
542
+ stream.onEnd(args[0], error);
543
+ }
544
+ break;
545
+ }
546
+ case RpcSystemCalls.ack: {
547
+ const sender = peer.sharedObjects.get(relatedId);
548
+ sender?.onAck(args[0], args[1]);
549
+ break;
550
+ }
551
+ case RpcSystemCalls.ackEnd: {
552
+ const sender = peer.sharedObjects.get(relatedId);
553
+ sender?.onAckEnd(args[0]);
554
+ break;
555
+ }
556
+ }
557
+ }
558
+ };
559
+
332
560
  // src/rpc-system-call-sender.ts
333
561
  var RpcSystemCallSender = class {
334
562
  handshake(conn, peerId, hubId, index) {
@@ -361,10 +589,186 @@ var RpcSystemCallSender = class {
361
589
  const msg = serializeMessage({ Method: RpcSystemCalls.keepAlive }, [activeCallIds]);
362
590
  conn.send(msg);
363
591
  }
592
+ item(conn, localId, index, item) {
593
+ const msg = serializeMessage(
594
+ { Method: RpcSystemCalls.item, RelatedId: localId },
595
+ [index, item]
596
+ );
597
+ conn.send(msg);
598
+ }
599
+ batch(conn, localId, index, items) {
600
+ const msg = serializeMessage(
601
+ { Method: RpcSystemCalls.batch, RelatedId: localId },
602
+ [index, items]
603
+ );
604
+ conn.send(msg);
605
+ }
606
+ end(conn, localId, index, error = null) {
607
+ const msg = serializeMessage(
608
+ { Method: RpcSystemCalls.end, RelatedId: localId },
609
+ [index, error]
610
+ );
611
+ conn.send(msg);
612
+ }
613
+ ack(conn, localId, nextIndex, hostId) {
614
+ const msg = serializeMessage(
615
+ { Method: RpcSystemCalls.ack, RelatedId: localId },
616
+ [nextIndex, hostId]
617
+ );
618
+ conn.send(msg);
619
+ }
620
+ ackEnd(conn, localId, hostId) {
621
+ const msg = serializeMessage(
622
+ { Method: RpcSystemCalls.ackEnd, RelatedId: localId },
623
+ [hostId]
624
+ );
625
+ conn.send(msg);
626
+ }
627
+ };
628
+
629
+ // src/rpc-stream-sender.ts
630
+ import { PromiseSource as PromiseSource4 } from "@actuallab/core";
631
+ var DEFAULT_ACK_PERIOD = 256;
632
+ var DEFAULT_ACK_ADVANCE = 128;
633
+ var RpcStreamSender = class {
634
+ id;
635
+ kind = RpcObjectKind.Local;
636
+ peer;
637
+ ackPeriod;
638
+ ackAdvance;
639
+ _nextIndex = 0;
640
+ _ended = false;
641
+ _started = new PromiseSource4();
642
+ constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE) {
643
+ const localId = peer.sharedObjects.nextId();
644
+ this.id = { hostId: peer.hub.hubId, localId };
645
+ this.peer = peer;
646
+ this.ackPeriod = ackPeriod;
647
+ this.ackAdvance = ackAdvance;
648
+ }
649
+ /** Returns the stream reference string for the $sys.Ok response. */
650
+ toRef() {
651
+ return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance}`;
652
+ }
653
+ /** Called by system call handler when $sys.Ack is received from the client. */
654
+ onAck(_nextIndex, _hostId) {
655
+ if (!this._started.isCompleted) {
656
+ this._started.resolve();
657
+ }
658
+ }
659
+ /** Called by system call handler when $sys.AckEnd is received from the client. */
660
+ onAckEnd(_hostId) {
661
+ this._ended = true;
662
+ this.peer.sharedObjects.unregister(this);
663
+ }
664
+ /** Send a single item to the client. */
665
+ sendItem(item) {
666
+ if (this._ended) return;
667
+ const conn = this.peer.connection;
668
+ if (!conn) return;
669
+ this.peer.hub.systemCallSender.item(conn, this.id.localId, this._nextIndex, item);
670
+ this._nextIndex++;
671
+ }
672
+ /** Send a batch of items to the client. */
673
+ sendBatch(items) {
674
+ if (this._ended || items.length === 0) return;
675
+ const conn = this.peer.connection;
676
+ if (!conn) return;
677
+ this.peer.hub.systemCallSender.batch(conn, this.id.localId, this._nextIndex, items);
678
+ this._nextIndex += items.length;
679
+ }
680
+ /** Signal stream completion to the client. */
681
+ sendEnd(error) {
682
+ if (this._ended) return;
683
+ this._ended = true;
684
+ const conn = this.peer.connection;
685
+ if (!conn) return;
686
+ const errorInfo = error ? { Message: error.message } : null;
687
+ this.peer.hub.systemCallSender.end(conn, this.id.localId, this._nextIndex, errorInfo);
688
+ }
689
+ /**
690
+ * Consume an AsyncIterable and send all items to the client.
691
+ * Waits for the client's initial Ack before starting to pump items.
692
+ */
693
+ async writeFrom(source) {
694
+ await this._started.promise;
695
+ try {
696
+ for await (const item of source) {
697
+ if (this._ended) return;
698
+ this.sendItem(item);
699
+ }
700
+ if (!this._ended) {
701
+ this.sendEnd();
702
+ }
703
+ } catch (e) {
704
+ if (!this._ended) {
705
+ this.sendEnd(e instanceof Error ? e : new Error(String(e)));
706
+ }
707
+ }
708
+ }
709
+ // -- IRpcObject --
710
+ reconnect() {
711
+ }
712
+ disconnect() {
713
+ this._ended = true;
714
+ if (!this._started.isCompleted) {
715
+ this._started.resolve();
716
+ }
717
+ this.peer.sharedObjects.unregister(this);
718
+ }
719
+ };
720
+
721
+ // src/rpc-remote-object-tracker.ts
722
+ var RpcRemoteObjectTracker = class {
723
+ _objects = /* @__PURE__ */ new Map();
724
+ register(obj) {
725
+ this._objects.set(obj.id.localId, obj);
726
+ }
727
+ get(localId) {
728
+ return this._objects.get(localId);
729
+ }
730
+ unregister(obj) {
731
+ this._objects.delete(obj.id.localId);
732
+ }
733
+ disconnectAll() {
734
+ for (const obj of this._objects.values()) {
735
+ obj.disconnect();
736
+ }
737
+ this._objects.clear();
738
+ }
739
+ reconnectAll() {
740
+ for (const obj of this._objects.values()) {
741
+ obj.reconnect();
742
+ }
743
+ }
744
+ };
745
+
746
+ // src/rpc-shared-object-tracker.ts
747
+ var RpcSharedObjectTracker = class {
748
+ _nextId = 1;
749
+ _objects = /* @__PURE__ */ new Map();
750
+ nextId() {
751
+ return this._nextId++;
752
+ }
753
+ register(obj) {
754
+ this._objects.set(obj.id.localId, obj);
755
+ }
756
+ get(localId) {
757
+ return this._objects.get(localId);
758
+ }
759
+ unregister(obj) {
760
+ this._objects.delete(obj.id.localId);
761
+ }
762
+ disconnectAll() {
763
+ for (const obj of this._objects.values()) {
764
+ obj.disconnect();
765
+ }
766
+ this._objects.clear();
767
+ }
364
768
  };
365
769
 
366
770
  // src/rpc-peer.ts
367
- import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource3 } from "@actuallab/core";
771
+ import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource5 } from "@actuallab/core";
368
772
 
369
773
  // src/rpc-client-peer-reconnect-delayer.ts
370
774
  import { RetryDelaySeq, RetryDelayer } from "@actuallab/core";
@@ -390,6 +794,8 @@ var RpcPeer = class {
390
794
  _connection;
391
795
  outbound = new RpcOutboundCallTracker();
392
796
  inbound = new RpcInboundCallTracker();
797
+ remoteObjects = new RpcRemoteObjectTracker();
798
+ sharedObjects = new RpcSharedObjectTracker();
393
799
  connected = new EventHandlerSet3();
394
800
  disconnected = new EventHandlerSet3();
395
801
  _pendingSends = [];
@@ -398,6 +804,9 @@ var RpcPeer = class {
398
804
  this.ref = ref;
399
805
  this._hub = hub;
400
806
  }
807
+ get hub() {
808
+ return this._hub;
809
+ }
401
810
  get connection() {
402
811
  return this._connection;
403
812
  }
@@ -463,6 +872,8 @@ var RpcPeer = class {
463
872
  }
464
873
  close() {
465
874
  this._stopKeepAlive();
875
+ this.remoteObjects.disconnectAll();
876
+ this.sharedObjects.disconnectAll();
466
877
  for (const call of this._pendingSends) {
467
878
  if (!call.result.isCompleted)
468
879
  call.result.reject(new Error("Peer closed."));
@@ -496,7 +907,7 @@ var RpcPeer = class {
496
907
  return;
497
908
  }
498
909
  if (method.startsWith("$sys")) {
499
- this._hub.handleSystemCall(message, args, this.outbound, this.inbound);
910
+ this._hub.systemCallHandler.handle(message, args, this);
500
911
  return;
501
912
  }
502
913
  this._handleInbound(message, args);
@@ -516,7 +927,12 @@ var RpcPeer = class {
516
927
  try {
517
928
  const context = this._connection !== void 0 ? { __rpcDispatch: true, callId: relatedId, connection: this._connection } : void 0;
518
929
  const result = await serviceHost.dispatch(method, args, context);
519
- if (!isNoWait && this._connection !== void 0) {
930
+ if (methodDef?.stream === true && this._connection !== void 0) {
931
+ const sender = new RpcStreamSender(this);
932
+ this.sharedObjects.register(sender);
933
+ this._hub.systemCallSender.ok(this._connection, relatedId, sender.toRef());
934
+ void sender.writeFrom(result);
935
+ } else if (!isNoWait && this._connection !== void 0) {
520
936
  this._hub.systemCallSender.ok(this._connection, relatedId, result);
521
937
  }
522
938
  } catch (e) {
@@ -601,7 +1017,7 @@ var RpcClientPeer = class extends RpcPeer {
601
1017
  const ws = wsFactory?.(connUrl) ?? new WebSocket(connUrl);
602
1018
  const conn = new RpcWebSocketConnection(ws);
603
1019
  this._connectionKind = 1 /* Connecting */;
604
- this._pendingHandshake = new PromiseSource3();
1020
+ this._pendingHandshake = new PromiseSource5();
605
1021
  this.setupConnection(conn);
606
1022
  this._connection = void 0;
607
1023
  const closedRejection = new Promise((_, reject) => conn.closed.add(() => reject(new Error("Connection failed"))));
@@ -657,6 +1073,11 @@ var RpcClientPeer = class extends RpcPeer {
657
1073
  _reconnect(_isPeerChanged) {
658
1074
  if (this._connection === void 0) return;
659
1075
  const conn = this._connection;
1076
+ if (_isPeerChanged) {
1077
+ this.remoteObjects.disconnectAll();
1078
+ } else {
1079
+ this.remoteObjects.reconnectAll();
1080
+ }
660
1081
  const trackerCalls = [...this.outbound.values()];
661
1082
  for (const call of trackerCalls) {
662
1083
  if (!call.removeOnOk && call.result.isCompleted) {
@@ -739,12 +1160,10 @@ function getMethodsMeta(cls) {
739
1160
  function getServiceMeta(cls) {
740
1161
  return cls[Symbol.metadata]?.[SERVICE_META];
741
1162
  }
742
- function rpcService(serviceName, options) {
1163
+ function rpcService(serviceName) {
743
1164
  return function(_target, context) {
744
1165
  const meta = context.metadata[SERVICE_META] ??= {};
745
1166
  meta.name = serviceName;
746
- if (options?.ctOffset !== void 0)
747
- meta.ctOffset = options.ctOffset;
748
1167
  };
749
1168
  }
750
1169
  function rpcMethod(options) {
@@ -754,8 +1173,7 @@ function rpcMethod(options) {
754
1173
  methods[methodName] = {
755
1174
  ...methods[methodName],
756
1175
  argCount: target.length,
757
- stream: options?.stream ?? false,
758
- noWait: options?.noWait ?? false
1176
+ returns: options?.returns
759
1177
  };
760
1178
  return target;
761
1179
  };
@@ -767,6 +1185,7 @@ var RpcHub = class {
767
1185
  peers = /* @__PURE__ */ new Map();
768
1186
  serviceHost;
769
1187
  systemCallSender = new RpcSystemCallSender();
1188
+ systemCallHandler = new RpcSystemCallHandler();
770
1189
  constructor(hubId) {
771
1190
  this.hubId = hubId ?? crypto.randomUUID();
772
1191
  this.serviceHost = new RpcServiceHost();
@@ -840,19 +1259,14 @@ var RpcHub = class {
840
1259
  for (const peer of this.peers.values()) peer.close();
841
1260
  this.peers.clear();
842
1261
  }
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
1262
  /** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */
848
1263
  _buildServiceDef(cls) {
849
1264
  const svcMeta = getServiceMeta(cls);
850
1265
  if (svcMeta === void 0) throw new Error("Contract class missing @rpcService metadata");
851
1266
  const methodsMeta = getMethodsMeta(cls) ?? {};
852
- const ctOffset = svcMeta.ctOffset ?? 0;
853
1267
  const methods = /* @__PURE__ */ new Map();
854
1268
  for (const [name, meta] of Object.entries(methodsMeta)) {
855
- const wireArgCount = meta.argCount + ctOffset;
1269
+ const wireArgCount = meta.argCount + 1;
856
1270
  const mapKey = `${name}:${wireArgCount}`;
857
1271
  methods.set(mapKey, {
858
1272
  name,
@@ -860,8 +1274,8 @@ var RpcHub = class {
860
1274
  argCount: meta.argCount,
861
1275
  wireArgCount,
862
1276
  callTypeId: 0,
863
- stream: meta.stream ?? false,
864
- noWait: meta.noWait ?? false
1277
+ stream: meta.returns === RpcType.stream,
1278
+ noWait: meta.returns === RpcType.noWait
865
1279
  });
866
1280
  }
867
1281
  return { name: svcMeta.name, methods };
@@ -885,11 +1299,24 @@ var RpcHub = class {
885
1299
  peer.callNoWait(wireName, callArgs);
886
1300
  };
887
1301
  }
1302
+ if (methodDef.stream) {
1303
+ return async (...args) => {
1304
+ const callArgs = args.slice(0, methodDef.argCount);
1305
+ const outboundCall = peer.call(wireName, callArgs);
1306
+ const result = await outboundCall.result.promise;
1307
+ const ref = parseStreamRef(result);
1308
+ if (!ref) throw new Error(`Expected stream reference, got: ${JSON.stringify(result)}`);
1309
+ const stream = new RpcStream(ref, peer);
1310
+ peer.remoteObjects.register(stream);
1311
+ return stream;
1312
+ };
1313
+ }
888
1314
  const callOptions = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
889
1315
  return async (...args) => {
890
1316
  const callArgs = args.slice(0, methodDef.argCount);
891
1317
  const outboundCall = peer.call(wireName, callArgs, callOptions);
892
- return outboundCall.result.promise;
1318
+ const result = await outboundCall.result.promise;
1319
+ return resolveStreamRefs(result, peer);
893
1320
  };
894
1321
  }
895
1322
  };
@@ -1099,14 +1526,20 @@ export {
1099
1526
  RpcInboundCall,
1100
1527
  RpcInboundCallTracker,
1101
1528
  RpcMessageChannelConnection,
1529
+ RpcObjectKind,
1102
1530
  RpcOutboundCall,
1103
1531
  RpcOutboundCallTracker,
1104
1532
  RpcPeer,
1105
1533
  RpcPeerConnectionKind,
1106
1534
  RpcPeerStateKind,
1107
1535
  RpcPeerStateMonitor,
1536
+ RpcRemoteObjectTracker,
1108
1537
  RpcServerPeer,
1109
1538
  RpcServiceHost,
1539
+ RpcSharedObjectTracker,
1540
+ RpcStream,
1541
+ RpcStreamSender,
1542
+ RpcSystemCallHandler,
1110
1543
  RpcSystemCallSender,
1111
1544
  RpcSystemCalls,
1112
1545
  RpcType,
@@ -1120,9 +1553,10 @@ export {
1120
1553
  getMethodsMeta,
1121
1554
  getServiceMeta,
1122
1555
  getStateDescription,
1123
- handleSystemCall,
1124
1556
  isConnected,
1125
1557
  likelyConnected,
1558
+ parseStreamRef,
1559
+ resolveStreamRefs,
1126
1560
  rpcMethod,
1127
1561
  rpcService,
1128
1562
  serializeFrame,