@actuallab/rpc 12.3.6 → 12.3.14

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
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  RpcPeerConnectionKind: () => RpcPeerConnectionKind,
53
53
  RpcPeerStateKind: () => RpcPeerStateKind,
54
54
  RpcPeerStateMonitor: () => RpcPeerStateMonitor,
55
+ RpcRemoteExecutionMode: () => RpcRemoteExecutionMode,
55
56
  RpcRemoteObjectTracker: () => RpcRemoteObjectTracker,
56
57
  RpcSerializationFormat: () => RpcSerializationFormat,
57
58
  RpcSerializationFormatResolver: () => RpcSerializationFormatResolver,
@@ -614,6 +615,18 @@ function createMessageChannelPair() {
614
615
  }
615
616
 
616
617
  // src/rpc-service-def.ts
618
+ var RpcRemoteExecutionMode = {
619
+ /** Wait for connection if disconnected when the call is made. */
620
+ AwaitForConnection: 1,
621
+ /** Resend on reconnection to the same peer. */
622
+ AllowReconnect: 2,
623
+ /** Resend on reconnection to a different peer (implies AllowReconnect). */
624
+ AllowResend: 6,
625
+ // 4 | AllowReconnect
626
+ /** Default: wait + reconnect + resend. */
627
+ Default: 7
628
+ // AwaitForConnection | AllowResend
629
+ };
617
630
  var RpcType = {
618
631
  object: /* @__PURE__ */ Symbol("object"),
619
632
  stream: /* @__PURE__ */ Symbol("stream"),
@@ -630,6 +643,7 @@ function defineRpcService(name, methods) {
630
643
  const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
631
644
  const wireArgCount = input.wireArgCount ?? input.args.length + 1;
632
645
  const mapKey = `${cleanName}:${wireArgCount}`;
646
+ const isNoWait = input.returns === RpcType.noWait;
633
647
  map.set(mapKey, {
634
648
  name: cleanName,
635
649
  serviceName: name,
@@ -637,7 +651,8 @@ function defineRpcService(name, methods) {
637
651
  wireArgCount,
638
652
  callTypeId: input.callTypeId ?? 0,
639
653
  stream: input.returns === RpcType.stream,
640
- noWait: input.returns === RpcType.noWait
654
+ noWait: isNoWait,
655
+ remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
641
656
  });
642
657
  }
643
658
  return { name, methods: map };
@@ -649,14 +664,17 @@ var RpcOutboundCall = class {
649
664
  callId;
650
665
  method;
651
666
  result = new import_core3.PromiseSource();
667
+ /** Bitfield of RpcRemoteExecutionMode flags controlling reconnect/resend behavior. */
668
+ remoteExecutionMode;
652
669
  /** Whether to remove this call from the tracker on $sys.Ok. Default: true.
653
670
  * Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */
654
671
  removeOnOk = true;
655
672
  /** The serialized wire data — stored for re-sending on reconnect. */
656
673
  serializedWireData = "";
657
- constructor(callId, method) {
674
+ constructor(callId, method, remoteExecutionMode = 7) {
658
675
  this.callId = callId;
659
676
  this.method = method;
677
+ this.remoteExecutionMode = remoteExecutionMode;
660
678
  }
661
679
  /** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */
662
680
  onDisconnect() {
@@ -769,6 +787,8 @@ var RpcStreamSender = class _RpcStreamSender {
769
787
  _lastAckedIndex = 0;
770
788
  _ackWaiting = null;
771
789
  _resetRequested = false;
790
+ _abortController = new AbortController();
791
+ _iterator = null;
772
792
  constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true) {
773
793
  const localId = peer.sharedObjects.nextId();
774
794
  this.id = { hostId: peer.hub.hubId, localId };
@@ -779,6 +799,10 @@ var RpcStreamSender = class _RpcStreamSender {
779
799
  this.ackPeriod = ackPeriod;
780
800
  this.ackAdvance = ackAdvance;
781
801
  }
802
+ /** AbortSignal that is aborted when the sender is disconnected. */
803
+ get abortSignal() {
804
+ return this._abortController.signal;
805
+ }
782
806
  /** Returns the stream reference string for the $sys.Ok response. */
783
807
  toRef() {
784
808
  return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
@@ -805,8 +829,7 @@ var RpcStreamSender = class _RpcStreamSender {
805
829
  }
806
830
  /** Called by system call handler when $sys.AckEnd is received from the client. */
807
831
  onAckEnd(_hostId) {
808
- this._ended = true;
809
- this.peer.sharedObjects.unregister(this);
832
+ this.disconnect();
810
833
  }
811
834
  /** Send a single item to the client. */
812
835
  sendItem(item) {
@@ -863,6 +886,7 @@ var RpcStreamSender = class _RpcStreamSender {
863
886
  async writeFrom(source) {
864
887
  await this._started.promise;
865
888
  const iterator = source[Symbol.asyncIterator]();
889
+ this._iterator = iterator;
866
890
  try {
867
891
  for (; ; ) {
868
892
  const next = await iterator.next();
@@ -927,10 +951,20 @@ var RpcStreamSender = class _RpcStreamSender {
927
951
  reconnect() {
928
952
  }
929
953
  disconnect() {
954
+ if (this._ended) return;
930
955
  this._ended = true;
956
+ this._abortController.abort();
931
957
  if (!this._started.isCompleted) {
932
958
  this._started.resolve();
933
959
  }
960
+ if (this._ackWaiting) {
961
+ this._ackWaiting.resolve();
962
+ this._ackWaiting = null;
963
+ }
964
+ if (this._iterator) {
965
+ void this._iterator.return?.(void 0);
966
+ this._iterator = null;
967
+ }
934
968
  this.peer.sharedObjects.unregister(this);
935
969
  }
936
970
  };
@@ -995,7 +1029,7 @@ var RpcStream = class _RpcStream {
995
1029
  _iterating = false;
996
1030
  _ackSentUpTo = 0;
997
1031
  constructor(sourceOrRef, optionsOrPeer) {
998
- if (_isAsyncIterable(sourceOrRef)) {
1032
+ if (_isStreamSource(sourceOrRef)) {
999
1033
  const options = optionsOrPeer ?? {};
1000
1034
  this.kind = RpcObjectKind.Local;
1001
1035
  this.localSource = sourceOrRef;
@@ -1056,7 +1090,8 @@ var RpcStream = class _RpcStream {
1056
1090
  );
1057
1091
  peer.sharedObjects.register(sender);
1058
1092
  this._sender = sender;
1059
- this._whenSent = sender.writeFrom(this.localSource);
1093
+ const source = typeof this.localSource === "function" ? this.localSource(sender.abortSignal) : this.localSource;
1094
+ this._whenSent = sender.writeFrom(source);
1060
1095
  this._toRefValue = peer.format.isBinary ? {
1061
1096
  SerializedId: [sender.id.hostId, sender.id.localId],
1062
1097
  AckPeriod: sender.ackPeriod,
@@ -1136,7 +1171,10 @@ var RpcStream = class _RpcStream {
1136
1171
  }
1137
1172
  }
1138
1173
  disconnect() {
1139
- if (this.kind === RpcObjectKind.Local) return;
1174
+ if (this.kind === RpcObjectKind.Local) {
1175
+ this._sender?.disconnect();
1176
+ return;
1177
+ }
1140
1178
  if (this._completed) return;
1141
1179
  this._completed = true;
1142
1180
  this._completionError = new Error("Peer disconnected.");
@@ -1145,7 +1183,8 @@ var RpcStream = class _RpcStream {
1145
1183
  // -- AsyncIterable --
1146
1184
  [Symbol.asyncIterator]() {
1147
1185
  if (this.kind === RpcObjectKind.Local) {
1148
- return this.localSource[Symbol.asyncIterator]();
1186
+ const source = typeof this.localSource === "function" ? this.localSource(new AbortController().signal) : this.localSource;
1187
+ return source[Symbol.asyncIterator]();
1149
1188
  }
1150
1189
  if (this._iterating)
1151
1190
  throw new Error("RpcStream can only be iterated once.");
@@ -1226,7 +1265,8 @@ var RpcStream = class _RpcStream {
1226
1265
  }
1227
1266
  }
1228
1267
  };
1229
- function _isAsyncIterable(value) {
1268
+ function _isStreamSource(value) {
1269
+ if (typeof value === "function") return true;
1230
1270
  return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
1231
1271
  }
1232
1272
  function resolveStreamRefs(value, peer) {
@@ -1761,7 +1801,7 @@ var RpcPeer = class {
1761
1801
  }
1762
1802
  call(method, args, options) {
1763
1803
  const callId = this.outbound.nextId();
1764
- const outboundCall = options?.outboundCallFactory ? options.outboundCallFactory(callId, method) : new RpcOutboundCall(callId, method);
1804
+ const outboundCall = options?.outboundCallFactory ? options.outboundCallFactory(callId, method) : new RpcOutboundCall(callId, method, options?.remoteExecutionMode ?? 7);
1765
1805
  const envelope = {
1766
1806
  Method: method,
1767
1807
  RelatedId: callId,
@@ -2098,7 +2138,18 @@ var RpcClientPeer = class extends RpcPeer {
2098
2138
  }
2099
2139
  const trackerCalls = [...this.outbound.values()];
2100
2140
  for (const call of trackerCalls) {
2101
- if (!call.removeOnOk && call.result.isCompleted) {
2141
+ const mode = call.remoteExecutionMode;
2142
+ if (!(mode & 2)) {
2143
+ if (!call.result.isCompleted)
2144
+ call.result.reject(new Error("Outbound call failed: disconnected and AllowReconnect is not set."));
2145
+ call.onDisconnect();
2146
+ this.outbound.remove(call.callId);
2147
+ } else if (_isPeerChanged && !(mode & 4)) {
2148
+ if (!call.result.isCompleted)
2149
+ call.result.reject(new Error("Outbound call failed: reconnected to a different peer and AllowResend is not set."));
2150
+ call.onDisconnect();
2151
+ this.outbound.remove(call.callId);
2152
+ } else if (!call.removeOnOk && call.result.isCompleted) {
2102
2153
  call.onDisconnect();
2103
2154
  this.outbound.remove(call.callId);
2104
2155
  } else {
@@ -2122,6 +2173,9 @@ var RpcServerPeer = class extends RpcPeer {
2122
2173
  /** Accept an incoming connection — sets up message handling and handshake response. */
2123
2174
  accept(conn) {
2124
2175
  this.setupConnection(conn);
2176
+ conn.closed.add(() => {
2177
+ this.sharedObjects.disconnectAll();
2178
+ });
2125
2179
  }
2126
2180
  _onHandshakeReceived(_handshake) {
2127
2181
  if (this._connection !== void 0) {
@@ -2201,7 +2255,8 @@ function rpcMethod(options) {
2201
2255
  methods[methodName] = {
2202
2256
  ...methods[methodName],
2203
2257
  argCount: target.length,
2204
- returns: options?.returns
2258
+ returns: options?.returns,
2259
+ remoteExecutionMode: options?.remoteExecutionMode
2205
2260
  };
2206
2261
  return target;
2207
2262
  };
@@ -2816,7 +2871,8 @@ var RpcHub = class {
2816
2871
  const methods = /* @__PURE__ */ new Map();
2817
2872
  for (const [name, byArgCount] of overloads) {
2818
2873
  if (byArgCount.size === 1) {
2819
- methods.set(name, byArgCount.values().next().value);
2874
+ const [[, singleFn]] = byArgCount;
2875
+ methods.set(name, singleFn);
2820
2876
  } else {
2821
2877
  methods.set(name, (...args) => {
2822
2878
  const fn = byArgCount.get(args.length);
@@ -2846,6 +2902,7 @@ var RpcHub = class {
2846
2902
  for (const [name, meta] of Object.entries(methodsMeta)) {
2847
2903
  const wireArgCount = meta.argCount + 1;
2848
2904
  const mapKey = `${name}:${wireArgCount}`;
2905
+ const isNoWait = meta.returns === RpcType.noWait;
2849
2906
  methods.set(mapKey, {
2850
2907
  name,
2851
2908
  serviceName: svcMeta.name,
@@ -2853,7 +2910,8 @@ var RpcHub = class {
2853
2910
  wireArgCount,
2854
2911
  callTypeId: 0,
2855
2912
  stream: meta.returns === RpcType.stream,
2856
- noWait: meta.returns === RpcType.noWait
2913
+ noWait: isNoWait,
2914
+ remoteExecutionMode: isNoWait ? 0 : meta.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
2857
2915
  });
2858
2916
  }
2859
2917
  return { name: svcMeta.name, methods };
@@ -2871,6 +2929,8 @@ var RpcHub = class {
2871
2929
  /** Create a client method for the given methodDef. Override in FusionHub for custom call types. */
2872
2930
  _createClientMethod(peer, methodDef) {
2873
2931
  const wireName = wireMethodName(methodDef);
2932
+ const mode = methodDef.remoteExecutionMode;
2933
+ const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);
2874
2934
  if (methodDef.noWait) {
2875
2935
  return (...args) => {
2876
2936
  const callArgs = args.slice(0, methodDef.argCount);
@@ -2879,6 +2939,8 @@ var RpcHub = class {
2879
2939
  }
2880
2940
  if (methodDef.stream) {
2881
2941
  return async (...args) => {
2942
+ if (mustCheckConnection && !peer.isConnected)
2943
+ throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
2882
2944
  const callArgs = args.slice(0, methodDef.argCount);
2883
2945
  const outboundCall = peer.call(wireName, callArgs);
2884
2946
  const result = await outboundCall.result.promise;
@@ -2892,8 +2954,10 @@ var RpcHub = class {
2892
2954
  return stream;
2893
2955
  };
2894
2956
  }
2895
- const callOptions = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
2957
+ const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
2896
2958
  return async (...args) => {
2959
+ if (mustCheckConnection && !peer.isConnected)
2960
+ throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
2897
2961
  const callArgs = args.slice(0, methodDef.argCount);
2898
2962
  const outboundCall = peer.call(wireName, callArgs, callOptions);
2899
2963
  const result = await outboundCall.result.promise;
@@ -2916,7 +2980,7 @@ function createRpcClient(peer, serviceDef) {
2916
2980
  const methods = /* @__PURE__ */ new Map();
2917
2981
  for (const [name, byArgCount] of overloads) {
2918
2982
  if (byArgCount.size === 1) {
2919
- const methodDef = byArgCount.values().next().value;
2983
+ const [[, methodDef]] = byArgCount;
2920
2984
  methods.set(name, createMethod(peer, methodDef));
2921
2985
  } else {
2922
2986
  const fns = /* @__PURE__ */ new Map();
@@ -2941,10 +3005,14 @@ function createRpcClient(peer, serviceDef) {
2941
3005
  });
2942
3006
  }
2943
3007
  function createMethod(peer, methodDef) {
3008
+ const mode = methodDef.remoteExecutionMode;
3009
+ const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);
2944
3010
  return async (...args) => {
3011
+ if (mustCheckConnection && !peer.isConnected)
3012
+ throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
2945
3013
  const callArgs = args.slice(0, methodDef.argCount);
2946
3014
  const wireName = wireMethodName(methodDef);
2947
- const options = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
3015
+ const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
2948
3016
  const outboundCall = peer.call(wireName, callArgs, options);
2949
3017
  return outboundCall.result.promise;
2950
3018
  };
@@ -3159,6 +3227,7 @@ var RpcPeerStateMonitor = class {
3159
3227
  RpcPeerConnectionKind,
3160
3228
  RpcPeerStateKind,
3161
3229
  RpcPeerStateMonitor,
3230
+ RpcRemoteExecutionMode,
3162
3231
  RpcRemoteObjectTracker,
3163
3232
  RpcSerializationFormat,
3164
3233
  RpcSerializationFormatResolver,