@actuallab/rpc 14.3.32 → 14.4.3

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.d.cts CHANGED
@@ -409,7 +409,8 @@ declare class RpcCallTimeouts {
409
409
  static readonly None: RpcCallTimeouts;
410
410
  /** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
411
411
  static readonly Command: RpcCallTimeouts;
412
- /** Max time a call may wait for the connection before it is sent. */
412
+ /** Max time a call may wait for a connection - before it is sent, and again for the
413
+ * duration of any outage it is still pending through. */
413
414
  readonly connectTimeoutMs: number;
414
415
  /** Max time a sent call may stay unanswered before it is failed. */
415
416
  readonly runTimeoutMs: number;
@@ -522,6 +523,10 @@ declare class RpcOutboundCallTracker {
522
523
  remove(callId: number): RpcOutboundCall | undefined;
523
524
  values(): IterableIterator<RpcOutboundCall>;
524
525
  activeCallIds(): number[];
526
+ /** Calls handed to the transport at least once. Must be snapshotted BEFORE the
527
+ * peer enters `Connected` — that flushes every queued call, making queued and
528
+ * in-flight calls indistinguishable. Mirrors .NET's `GetSentCalls`. */
529
+ getSentCalls(): RpcOutboundCall[];
525
530
  /** Reject all pending calls with the given error.
526
531
  * Stage-3 compute calls (result resolved, awaiting invalidation) are kept in the tracker. */
527
532
  rejectAll(error: Error): void;
@@ -999,6 +1004,8 @@ declare abstract class RpcPeer {
999
1004
  /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
1000
1005
  * for the current connection. Useful for diagnostics. */
1001
1006
  private _lastKeepAliveAt;
1007
+ /** When the current outage began; 0 until the peer has been connected at least once. */
1008
+ private _disconnectedAt;
1002
1009
  /** Outbound keep-alive send period. Initialized from `hub.limits` at
1003
1010
  * construction; set directly to override on a single peer. */
1004
1011
  keepAlivePeriodMs: number;
@@ -1008,9 +1015,10 @@ declare abstract class RpcPeer {
1008
1015
  protected constructor(hub: RpcHub, ref: string);
1009
1016
  /** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
1010
1017
  * the connect timeout while a call still awaits the wire, the run timeout
1011
- * once it has been sent. Mirrors .NET `RpcOutboundCallTracker.Maintain`
1012
- * (RpcCallTrackers.cs:108-207). Calls without timeouts (queries) never
1013
- * time out here. */
1018
+ * once it has been sent, and the connect timeout again for any pending call while the
1019
+ * peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,
1020
+ * per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts
1021
+ * (queries) never time out here. */
1014
1022
  private _maintainOutboundCalls;
1015
1023
  get connection(): RpcConnection | undefined;
1016
1024
  get connectionState(): RpcConnectionState;
@@ -1071,6 +1079,14 @@ declare const enum RpcConnectionState {
1071
1079
  Handshaking = 2,
1072
1080
  Connected = 3
1073
1081
  }
1082
+ /** Outcome of comparing an incoming handshake with the previous one. Mirrors
1083
+ * .NET `RpcPeerChangeKind` — `ChangedToVeryFirst` lets the first handshake skip
1084
+ * reconnect processing, since there's no prior connection to reconcile with. */
1085
+ declare const enum RpcPeerChangeKind {
1086
+ Unchanged = 0,
1087
+ ChangedToVeryFirst = 1,
1088
+ Changed = 2
1089
+ }
1074
1090
  /** Client-side RPC peer — initiates WebSocket connection. ref = URL. */
1075
1091
  declare class RpcClientPeer extends RpcPeer {
1076
1092
  private _disposed;
@@ -1084,7 +1100,10 @@ declare class RpcClientPeer extends RpcPeer {
1084
1100
  * `connectionState.Handshake.Index` path in
1085
1101
  * `RpcOutboundCallTracker.TryReconnect`. */
1086
1102
  private _remoteHandshakeIndex;
1087
- private _lastRemotePeerId;
1103
+ /** Mirrors .NET's `lastHandshake` local in `RpcPeer.OnRun`: first-ness is keyed
1104
+ * off "was there a previous handshake", not off `RemotePeerId` — a legacy server
1105
+ * sends none, so every one of its handshakes would look like the first. */
1106
+ private _lastHandshake;
1088
1107
  private _pendingHandshake;
1089
1108
  private _reconnectsAt;
1090
1109
  /** Server-issued reconnect secret, from the most recent handshake that
@@ -1166,7 +1185,10 @@ declare class RpcClientPeer extends RpcPeer {
1166
1185
  protected run(): Promise<void>;
1167
1186
  private _runImpl;
1168
1187
  /**
1169
- * Reconcile outbound calls after reconnection, then flush pending sends.
1188
+ * Reconcile the calls that were in flight on the connection that just died
1189
+ * (`sentCalls`), then resend the survivors. Calls still waiting for a
1190
+ * connection are deliberately NOT passed in — `AllowReconnect`/`AllowResend`
1191
+ * don't apply to them, and entering `Connected` already sends them.
1170
1192
  *
1171
1193
  * On peer change (different server hubId) or with binary wire format —
1172
1194
  * we blind-resend every eligible call.
@@ -1204,7 +1226,7 @@ declare class RpcClientPeer extends RpcPeer {
1204
1226
  * `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not
1205
1227
  * `RemoteHubId` (an idle server peer can be replaced while keeping the
1206
1228
  * same hub id). On a change it clears the inbound tracker and fires
1207
- * `peerChanged`; the very first handshake is never a change. */
1229
+ * `peerChanged`; the very first handshake is `ChangedToVeryFirst`. */
1208
1230
  private _detectPeerChange;
1209
1231
  protected _onHandshakeReceived(handshake: RemoteHandshake): void;
1210
1232
  close(): void;
@@ -1620,4 +1642,4 @@ declare function xxh3_64str(input: string, seed?: bigint): bigint;
1620
1642
  */
1621
1643
  declare function computeMethodHash(methodName: string): number;
1622
1644
 
1623
- export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, REMOTE_EXCEPTION_TYPE_REF, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTimeouts, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcError, type RpcErrorFilter, type RpcExceptionInfo, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, RpcLimits, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, type RpcPeerFactory, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, type RpcReconnectProofParameters, RpcRefBuilder, RpcRemoteExecutionMode, RpcRemoteObjectTracker, RpcSerializationFormat, RpcSerializationFormatResolver, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamOptions, type RpcStreamRef, RpcStreamSender, type RpcStreamSource, RpcSystemCallHandler, RpcSystemCallSender, RpcSystemCalls, RpcType, RpcWebSocketConnection, type RpcWireData, type ServiceMeta, type WebSocketLike, type WebSocketServer, WebSocketState, computeMethodHash, computeReconnectProof, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, genericErrorFilter, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, isReconnectProofSupported, likelyConnected, parseStreamRef, readPolymorphismMarker, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, toExceptionInfo, toRpcStream, wireMethodName, xxh3_64, xxh3_64str };
1645
+ export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, REMOTE_EXCEPTION_TYPE_REF, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTimeouts, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcError, type RpcErrorFilter, type RpcExceptionInfo, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, RpcLimits, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerChangeKind, type RpcPeerFactory, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, type RpcReconnectProofParameters, RpcRefBuilder, RpcRemoteExecutionMode, RpcRemoteObjectTracker, RpcSerializationFormat, RpcSerializationFormatResolver, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamOptions, type RpcStreamRef, RpcStreamSender, type RpcStreamSource, RpcSystemCallHandler, RpcSystemCallSender, RpcSystemCalls, RpcType, RpcWebSocketConnection, type RpcWireData, type ServiceMeta, type WebSocketLike, type WebSocketServer, WebSocketState, computeMethodHash, computeReconnectProof, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, genericErrorFilter, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, isReconnectProofSupported, likelyConnected, parseStreamRef, readPolymorphismMarker, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, toExceptionInfo, toRpcStream, wireMethodName, xxh3_64, xxh3_64str };
package/dist/index.d.ts CHANGED
@@ -409,7 +409,8 @@ declare class RpcCallTimeouts {
409
409
  static readonly None: RpcCallTimeouts;
410
410
  /** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
411
411
  static readonly Command: RpcCallTimeouts;
412
- /** Max time a call may wait for the connection before it is sent. */
412
+ /** Max time a call may wait for a connection - before it is sent, and again for the
413
+ * duration of any outage it is still pending through. */
413
414
  readonly connectTimeoutMs: number;
414
415
  /** Max time a sent call may stay unanswered before it is failed. */
415
416
  readonly runTimeoutMs: number;
@@ -522,6 +523,10 @@ declare class RpcOutboundCallTracker {
522
523
  remove(callId: number): RpcOutboundCall | undefined;
523
524
  values(): IterableIterator<RpcOutboundCall>;
524
525
  activeCallIds(): number[];
526
+ /** Calls handed to the transport at least once. Must be snapshotted BEFORE the
527
+ * peer enters `Connected` — that flushes every queued call, making queued and
528
+ * in-flight calls indistinguishable. Mirrors .NET's `GetSentCalls`. */
529
+ getSentCalls(): RpcOutboundCall[];
525
530
  /** Reject all pending calls with the given error.
526
531
  * Stage-3 compute calls (result resolved, awaiting invalidation) are kept in the tracker. */
527
532
  rejectAll(error: Error): void;
@@ -999,6 +1004,8 @@ declare abstract class RpcPeer {
999
1004
  /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
1000
1005
  * for the current connection. Useful for diagnostics. */
1001
1006
  private _lastKeepAliveAt;
1007
+ /** When the current outage began; 0 until the peer has been connected at least once. */
1008
+ private _disconnectedAt;
1002
1009
  /** Outbound keep-alive send period. Initialized from `hub.limits` at
1003
1010
  * construction; set directly to override on a single peer. */
1004
1011
  keepAlivePeriodMs: number;
@@ -1008,9 +1015,10 @@ declare abstract class RpcPeer {
1008
1015
  protected constructor(hub: RpcHub, ref: string);
1009
1016
  /** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
1010
1017
  * the connect timeout while a call still awaits the wire, the run timeout
1011
- * once it has been sent. Mirrors .NET `RpcOutboundCallTracker.Maintain`
1012
- * (RpcCallTrackers.cs:108-207). Calls without timeouts (queries) never
1013
- * time out here. */
1018
+ * once it has been sent, and the connect timeout again for any pending call while the
1019
+ * peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,
1020
+ * per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts
1021
+ * (queries) never time out here. */
1014
1022
  private _maintainOutboundCalls;
1015
1023
  get connection(): RpcConnection | undefined;
1016
1024
  get connectionState(): RpcConnectionState;
@@ -1071,6 +1079,14 @@ declare const enum RpcConnectionState {
1071
1079
  Handshaking = 2,
1072
1080
  Connected = 3
1073
1081
  }
1082
+ /** Outcome of comparing an incoming handshake with the previous one. Mirrors
1083
+ * .NET `RpcPeerChangeKind` — `ChangedToVeryFirst` lets the first handshake skip
1084
+ * reconnect processing, since there's no prior connection to reconcile with. */
1085
+ declare const enum RpcPeerChangeKind {
1086
+ Unchanged = 0,
1087
+ ChangedToVeryFirst = 1,
1088
+ Changed = 2
1089
+ }
1074
1090
  /** Client-side RPC peer — initiates WebSocket connection. ref = URL. */
1075
1091
  declare class RpcClientPeer extends RpcPeer {
1076
1092
  private _disposed;
@@ -1084,7 +1100,10 @@ declare class RpcClientPeer extends RpcPeer {
1084
1100
  * `connectionState.Handshake.Index` path in
1085
1101
  * `RpcOutboundCallTracker.TryReconnect`. */
1086
1102
  private _remoteHandshakeIndex;
1087
- private _lastRemotePeerId;
1103
+ /** Mirrors .NET's `lastHandshake` local in `RpcPeer.OnRun`: first-ness is keyed
1104
+ * off "was there a previous handshake", not off `RemotePeerId` — a legacy server
1105
+ * sends none, so every one of its handshakes would look like the first. */
1106
+ private _lastHandshake;
1088
1107
  private _pendingHandshake;
1089
1108
  private _reconnectsAt;
1090
1109
  /** Server-issued reconnect secret, from the most recent handshake that
@@ -1166,7 +1185,10 @@ declare class RpcClientPeer extends RpcPeer {
1166
1185
  protected run(): Promise<void>;
1167
1186
  private _runImpl;
1168
1187
  /**
1169
- * Reconcile outbound calls after reconnection, then flush pending sends.
1188
+ * Reconcile the calls that were in flight on the connection that just died
1189
+ * (`sentCalls`), then resend the survivors. Calls still waiting for a
1190
+ * connection are deliberately NOT passed in — `AllowReconnect`/`AllowResend`
1191
+ * don't apply to them, and entering `Connected` already sends them.
1170
1192
  *
1171
1193
  * On peer change (different server hubId) or with binary wire format —
1172
1194
  * we blind-resend every eligible call.
@@ -1204,7 +1226,7 @@ declare class RpcClientPeer extends RpcPeer {
1204
1226
  * `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not
1205
1227
  * `RemoteHubId` (an idle server peer can be replaced while keeping the
1206
1228
  * same hub id). On a change it clears the inbound tracker and fires
1207
- * `peerChanged`; the very first handshake is never a change. */
1229
+ * `peerChanged`; the very first handshake is `ChangedToVeryFirst`. */
1208
1230
  private _detectPeerChange;
1209
1231
  protected _onHandshakeReceived(handshake: RemoteHandshake): void;
1210
1232
  close(): void;
@@ -1620,4 +1642,4 @@ declare function xxh3_64str(input: string, seed?: bigint): bigint;
1620
1642
  */
1621
1643
  declare function computeMethodHash(methodName: string): number;
1622
1644
 
1623
- export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, REMOTE_EXCEPTION_TYPE_REF, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTimeouts, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcError, type RpcErrorFilter, type RpcExceptionInfo, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, RpcLimits, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, type RpcPeerFactory, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, type RpcReconnectProofParameters, RpcRefBuilder, RpcRemoteExecutionMode, RpcRemoteObjectTracker, RpcSerializationFormat, RpcSerializationFormatResolver, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamOptions, type RpcStreamRef, RpcStreamSender, type RpcStreamSource, RpcSystemCallHandler, RpcSystemCallSender, RpcSystemCalls, RpcType, RpcWebSocketConnection, type RpcWireData, type ServiceMeta, type WebSocketLike, type WebSocketServer, WebSocketState, computeMethodHash, computeReconnectProof, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, genericErrorFilter, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, isReconnectProofSupported, likelyConnected, parseStreamRef, readPolymorphismMarker, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, toExceptionInfo, toRpcStream, wireMethodName, xxh3_64, xxh3_64str };
1645
+ export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, REMOTE_EXCEPTION_TYPE_REF, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTimeouts, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcError, type RpcErrorFilter, type RpcExceptionInfo, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, RpcLimits, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerChangeKind, type RpcPeerFactory, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, type RpcReconnectProofParameters, RpcRefBuilder, RpcRemoteExecutionMode, RpcRemoteObjectTracker, RpcSerializationFormat, RpcSerializationFormatResolver, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamOptions, type RpcStreamRef, RpcStreamSender, type RpcStreamSource, RpcSystemCallHandler, RpcSystemCallSender, RpcSystemCalls, RpcType, RpcWebSocketConnection, type RpcWireData, type ServiceMeta, type WebSocketLike, type WebSocketServer, WebSocketState, computeMethodHash, computeReconnectProof, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, genericErrorFilter, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, isReconnectProofSupported, likelyConnected, parseStreamRef, readPolymorphismMarker, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, toExceptionInfo, toRpcStream, wireMethodName, xxh3_64, xxh3_64str };
package/dist/index.js CHANGED
@@ -751,6 +751,16 @@ var RpcOutboundCallTracker = class {
751
751
  activeCallIds() {
752
752
  return [...this._calls.keys()];
753
753
  }
754
+ /** Calls handed to the transport at least once. Must be snapshotted BEFORE the
755
+ * peer enters `Connected` — that flushes every queued call, making queued and
756
+ * in-flight calls indistinguishable. Mirrors .NET's `GetSentCalls`. */
757
+ getSentCalls() {
758
+ const calls = [];
759
+ for (const call of this._calls.values())
760
+ if (call.sentAt !== 0)
761
+ calls.push(call);
762
+ return calls;
763
+ }
754
764
  /** Reject all pending calls with the given error.
755
765
  * Stage-3 compute calls (result resolved, awaiting invalidation) are kept in the tracker. */
756
766
  rejectAll(error) {
@@ -869,7 +879,8 @@ var RpcCallTimeouts = class _RpcCallTimeouts {
869
879
  static None = new _RpcCallTimeouts(Infinity, Infinity);
870
880
  /** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
871
881
  static Command = new _RpcCallTimeouts(1500, 1e4);
872
- /** Max time a call may wait for the connection before it is sent. */
882
+ /** Max time a call may wait for a connection - before it is sent, and again for the
883
+ * duration of any outage it is still pending through. */
873
884
  connectTimeoutMs;
874
885
  /** Max time a sent call may stay unanswered before it is failed. */
875
886
  runTimeoutMs;
@@ -2663,6 +2674,8 @@ var RpcPeer = class {
2663
2674
  /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
2664
2675
  * for the current connection. Useful for diagnostics. */
2665
2676
  _lastKeepAliveAt = 0;
2677
+ /** When the current outage began; 0 until the peer has been connected at least once. */
2678
+ _disconnectedAt = 0;
2666
2679
  /** Outbound keep-alive send period. Initialized from `hub.limits` at
2667
2680
  * construction; set directly to override on a single peer. */
2668
2681
  keepAlivePeriodMs;
@@ -2683,9 +2696,10 @@ var RpcPeer = class {
2683
2696
  }
2684
2697
  /** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
2685
2698
  * the connect timeout while a call still awaits the wire, the run timeout
2686
- * once it has been sent. Mirrors .NET `RpcOutboundCallTracker.Maintain`
2687
- * (RpcCallTrackers.cs:108-207). Calls without timeouts (queries) never
2688
- * time out here. */
2699
+ * once it has been sent, and the connect timeout again for any pending call while the
2700
+ * peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,
2701
+ * per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts
2702
+ * (queries) never time out here. */
2689
2703
  _maintainOutboundCalls() {
2690
2704
  const now = Date.now();
2691
2705
  const canRunTimeout = this._isConnected && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);
@@ -2694,7 +2708,11 @@ var RpcPeer = class {
2694
2708
  if (timeouts === void 0 || call.result.isCompleted)
2695
2709
  continue;
2696
2710
  let timedOut;
2697
- if (call.sentAt === 0) {
2711
+ if (!this._isConnected) {
2712
+ const since = Math.max(this._disconnectedAt, call.startedAt);
2713
+ if (now - since >= timeouts.connectTimeoutMs)
2714
+ timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
2715
+ } else if (call.sentAt === 0) {
2698
2716
  if (now - call.startedAt >= timeouts.connectTimeoutMs)
2699
2717
  timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
2700
2718
  } else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)
@@ -2737,10 +2755,13 @@ var RpcPeer = class {
2737
2755
  this._connectionState = value;
2738
2756
  if (value === 3 /* Connected */) {
2739
2757
  this._isConnected = true;
2758
+ this._disconnectedAt = 0;
2740
2759
  this._armKeepAliveWatchdog();
2741
2760
  } else if (value === 0 /* Disconnected */) {
2742
- if (previousState === 3 /* Connected */)
2761
+ if (previousState === 3 /* Connected */) {
2743
2762
  this._isConnected = false;
2763
+ this._disconnectedAt = Date.now();
2764
+ }
2744
2765
  this._disarmKeepAliveWatchdog();
2745
2766
  }
2746
2767
  this.connectionStateChanged.trigger(value);
@@ -2765,6 +2786,8 @@ var RpcPeer = class {
2765
2786
  conn.closed.add((ev) => {
2766
2787
  if (this._connection !== conn) return;
2767
2788
  this._connection = void 0;
2789
+ if (this._isConnected)
2790
+ this._disconnectedAt = Date.now();
2768
2791
  this._isConnected = false;
2769
2792
  if (ev.reason)
2770
2793
  infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);
@@ -3065,6 +3088,13 @@ var RpcConnectionState = /* @__PURE__ */ ((RpcConnectionState2) => {
3065
3088
  RpcConnectionState2[RpcConnectionState2["Connected"] = 3] = "Connected";
3066
3089
  return RpcConnectionState2;
3067
3090
  })(RpcConnectionState || {});
3091
+ var RpcPeerChangeKind = /* @__PURE__ */ ((RpcPeerChangeKind2) => {
3092
+ RpcPeerChangeKind2[RpcPeerChangeKind2["Unchanged"] = 0] = "Unchanged";
3093
+ RpcPeerChangeKind2[RpcPeerChangeKind2["ChangedToVeryFirst"] = 1] = "ChangedToVeryFirst";
3094
+ RpcPeerChangeKind2[RpcPeerChangeKind2["Changed"] = 2] = "Changed";
3095
+ return RpcPeerChangeKind2;
3096
+ })(RpcPeerChangeKind || {});
3097
+ var peerChangeKindNames = ["Unchanged", "ChangedToVeryFirst", "Changed"];
3068
3098
  var RpcClientPeer = class extends RpcPeer {
3069
3099
  _disposed = false;
3070
3100
  /** Aborted when the peer is stopped/disposed — threaded into the reconnect
@@ -3077,7 +3107,10 @@ var RpcClientPeer = class extends RpcPeer {
3077
3107
  * `connectionState.Handshake.Index` path in
3078
3108
  * `RpcOutboundCallTracker.TryReconnect`. */
3079
3109
  _remoteHandshakeIndex = 0;
3080
- _lastRemotePeerId;
3110
+ /** Mirrors .NET's `lastHandshake` local in `RpcPeer.OnRun`: first-ness is keyed
3111
+ * off "was there a previous handshake", not off `RemotePeerId` — a legacy server
3112
+ * sends none, so every one of its handshakes would look like the first. */
3113
+ _lastHandshake;
3081
3114
  _pendingHandshake;
3082
3115
  _reconnectsAt = 0;
3083
3116
  /** Server-issued reconnect secret, from the most recent handshake that
@@ -3158,10 +3191,11 @@ var RpcClientPeer = class extends RpcPeer {
3158
3191
  * self-invalidate and remote/shared objects are disposed). Pass `false`
3159
3192
  * to simulate a same-peer reconnect in tests. */
3160
3193
  connectWith(conn, isPeerChanged = true) {
3194
+ const sentCalls = this.outboundCalls.getSentCalls();
3161
3195
  this.setupConnection(conn);
3162
3196
  this._isConnected = true;
3163
3197
  this.connectionStateChanged.trigger(3 /* Connected */);
3164
- void this._reconnect(isPeerChanged);
3198
+ void this._reconnect(sentCalls, isPeerChanged);
3165
3199
  }
3166
3200
  /** Kick off the reconnect loop. Idempotent — subsequent calls are
3167
3201
  * no-ops; await `peer.whenRunning` if you need the loop's completion
@@ -3292,11 +3326,19 @@ var RpcClientPeer = class extends RpcPeer {
3292
3326
  this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
3293
3327
  if (remoteHandshake.Secret)
3294
3328
  this._reconnectSecret = remoteHandshake.Secret;
3295
- const isPeerChanged = this._detectPeerChange(remoteHandshake);
3296
- infoLog?.log(`'${this.ref}': Handshake succeeded, peerChanged=${isPeerChanged}`);
3329
+ const peerChangeKind = this._detectPeerChange(remoteHandshake);
3330
+ infoLog?.log(
3331
+ `'${this.ref}': Handshake succeeded, peerChangeKind=${peerChangeKindNames[peerChangeKind]}`
3332
+ );
3333
+ const sentCalls = this.outboundCalls.getSentCalls();
3297
3334
  this._setConnectionState(3 /* Connected */);
3298
3335
  connectedAt = Date.now();
3299
- await this._reconnect(isPeerChanged, closedController.signal);
3336
+ if (peerChangeKind !== 1 /* ChangedToVeryFirst */)
3337
+ await this._reconnect(
3338
+ sentCalls,
3339
+ peerChangeKind === 2 /* Changed */,
3340
+ closedController.signal
3341
+ );
3300
3342
  await whenClosed;
3301
3343
  } catch (e) {
3302
3344
  debugLog2?.log(`'${this.ref}': run() iteration failed:`, e);
@@ -3318,7 +3360,10 @@ var RpcClientPeer = class extends RpcPeer {
3318
3360
  }
3319
3361
  }
3320
3362
  /**
3321
- * Reconcile outbound calls after reconnection, then flush pending sends.
3363
+ * Reconcile the calls that were in flight on the connection that just died
3364
+ * (`sentCalls`), then resend the survivors. Calls still waiting for a
3365
+ * connection are deliberately NOT passed in — `AllowReconnect`/`AllowResend`
3366
+ * don't apply to them, and entering `Connected` already sends them.
3322
3367
  *
3323
3368
  * On peer change (different server hubId) or with binary wire format —
3324
3369
  * we blind-resend every eligible call.
@@ -3336,7 +3381,7 @@ var RpcClientPeer = class extends RpcPeer {
3336
3381
  * are always self-invalidated — TS doesn't track compute-call stages
3337
3382
  * across reconnect, so the safest behavior is to force a fresh compute.
3338
3383
  */
3339
- async _reconnect(isPeerChanged, closedSignal) {
3384
+ async _reconnect(sentCalls, isPeerChanged, closedSignal) {
3340
3385
  if (this._connection === void 0) return;
3341
3386
  if (isPeerChanged) {
3342
3387
  this.remoteObjects.disconnectAll();
@@ -3345,7 +3390,7 @@ var RpcClientPeer = class extends RpcPeer {
3345
3390
  this.remoteObjects.reconnectAll();
3346
3391
  }
3347
3392
  const eligible = [];
3348
- for (const call of [...this.outboundCalls.values()]) {
3393
+ for (const call of sentCalls) {
3349
3394
  const mode = call.remoteExecutionMode;
3350
3395
  if (!(mode & 2)) {
3351
3396
  if (!call.result.isCompleted)
@@ -3366,8 +3411,7 @@ var RpcClientPeer = class extends RpcPeer {
3366
3411
  continue;
3367
3412
  }
3368
3413
  eligible.push(call);
3369
- if (call.sentAt !== 0)
3370
- call.sentAt = Date.now();
3414
+ call.sentAt = Date.now();
3371
3415
  }
3372
3416
  let unknownIds = null;
3373
3417
  if (!isPeerChanged && eligible.length > 0) {
@@ -3455,19 +3499,18 @@ var RpcClientPeer = class extends RpcPeer {
3455
3499
  * `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not
3456
3500
  * `RemoteHubId` (an idle server peer can be replaced while keeping the
3457
3501
  * same hub id). On a change it clears the inbound tracker and fires
3458
- * `peerChanged`; the very first handshake is never a change. */
3502
+ * `peerChanged`; the very first handshake is `ChangedToVeryFirst`. */
3459
3503
  _detectPeerChange(handshake) {
3460
- const remotePeerId = handshake.RemotePeerId;
3461
- if (remotePeerId === void 0)
3462
- return false;
3463
- const isPeerChanged = this._lastRemotePeerId !== void 0 && this._lastRemotePeerId !== remotePeerId;
3464
- this._lastRemotePeerId = remotePeerId;
3465
- if (isPeerChanged) {
3466
- this.inboundCalls.clear();
3467
- infoLog?.log(`'${this.ref}': Peer changed`);
3468
- this.peerChanged.trigger();
3469
- }
3470
- return isPeerChanged;
3504
+ const lastHandshake = this._lastHandshake;
3505
+ this._lastHandshake = handshake;
3506
+ if (lastHandshake === void 0)
3507
+ return 1 /* ChangedToVeryFirst */;
3508
+ if (handshake.RemotePeerId === lastHandshake.RemotePeerId)
3509
+ return 0 /* Unchanged */;
3510
+ this.inboundCalls.clear();
3511
+ infoLog?.log(`'${this.ref}': Peer changed`);
3512
+ this.peerChanged.trigger();
3513
+ return 2 /* Changed */;
3471
3514
  }
3472
3515
  _onHandshakeReceived(handshake) {
3473
3516
  this._pendingHandshake?.resolve(handshake);
@@ -4766,6 +4809,7 @@ export {
4766
4809
  RpcOutboundCall,
4767
4810
  RpcOutboundCallTracker,
4768
4811
  RpcPeer,
4812
+ RpcPeerChangeKind,
4769
4813
  RpcPeerStateKind,
4770
4814
  RpcPeerStateMonitor,
4771
4815
  RpcRefBuilder,