@actuallab/rpc 12.3.33 → 12.3.50

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
@@ -638,6 +638,11 @@ declare class RpcSystemCallSender {
638
638
  ackEnd(conn: RpcConnection, format: RpcSerializationFormat, localId: number, hostId: string): void;
639
639
  }
640
640
 
641
+ /** Factory signature shared by all peer lookup/creation methods. Serialization
642
+ * format is encoded in the URL via `?f=...` (see {@link RpcPeerRefBuilder}),
643
+ * so the factory doesn't need an extra parameter for it. `getClientPeer` /
644
+ * `getServerPeer` cast the result, so the factory returns RpcPeer. */
645
+ type RpcPeerFactory = (hub: RpcHub, ref: string) => RpcPeer;
641
646
  /** Central RPC coordinator — manages peers, services, and configuration. */
642
647
  declare class RpcHub {
643
648
  readonly hubId: string;
@@ -648,15 +653,37 @@ declare class RpcHub {
648
653
  /** Method registry for compact format hash ↔ name resolution.
649
654
  * Created lazily, populated by addService/addClient. */
650
655
  readonly registry: RpcMethodRegistry;
656
+ /** Shared reconnect delayer used by every client peer in this hub. Swap in
657
+ * a custom subclass (e.g. app-level signal-gated) before peers start. */
658
+ reconnectDelayer: RpcClientPeerReconnectDelayer;
659
+ /** URL used by {@link defaultPeer} to resolve / create the default client
660
+ * peer. Must be set before the first {@link defaultPeer} access. */
661
+ defaultPeerUrl: string | undefined;
662
+ /** Factory used by {@link defaultPeer} when the peer needs to be created.
663
+ * If left undefined, `getPeer`'s built-in default (RpcClientPeer with
664
+ * format derived from URL or resolver default) is used. */
665
+ defaultPeerFactory: RpcPeerFactory | undefined;
651
666
  constructor(hubId?: string);
667
+ /** Shortcut for the hub's default client peer. Fails if
668
+ * {@link defaultPeerUrl} is not set. */
669
+ get defaultPeer(): RpcClientPeer;
670
+ /** Register a peer under its ref. If a different peer is already registered
671
+ * at the same ref, close it first — the hub guarantees at most one live
672
+ * peer per ref, and no peer exists outside the hub. */
652
673
  addPeer(peer: RpcPeer): void;
653
674
  removePeer(ref: string): void;
654
- /** Get or create a peer by ref. Server peers have "server://" prefix, everything else is a client peer. */
655
- getPeer(ref: string): RpcPeer;
656
- /** Get or create a client peer for the given URL. */
657
- getClientPeer(ref: string): RpcClientPeer;
658
- /** Get or create a server peer for the given ref. */
659
- getServerPeer(ref: string): RpcServerPeer;
675
+ /** Get or create a peer by ref. If the peer does not exist:
676
+ * - with `factory` provided: it is constructed via `factory(this, ref)`;
677
+ * - otherwise: server peers (ref starts with `server://`) get a plain
678
+ * RpcServerPeer, anything else gets a default-configured RpcClientPeer.
679
+ * The created peer is always registered in this hub. */
680
+ getPeer(ref: string, factory?: RpcPeerFactory): RpcPeer;
681
+ /** Get or create a client peer for the given URL. See {@link getPeer} for
682
+ * the factory semantics. */
683
+ getClientPeer(ref: string, factory?: RpcPeerFactory): RpcClientPeer;
684
+ /** Get or create a server peer for the given ref. See {@link getPeer} for
685
+ * the factory semantics. */
686
+ getServerPeer(ref: string, factory?: RpcPeerFactory): RpcServerPeer;
660
687
  /** Register a service with optional server method wrapping for custom call types. */
661
688
  addService(defOrContract: RpcServiceDef | (AnyConstructor), impl: RpcServiceImpl): void;
662
689
  /** Create a typed client proxy for a service on a remote peer. */
@@ -712,9 +739,13 @@ declare class RpcSharedObjectTracker {
712
739
 
713
740
  /** WebSocket close code sent by the server when the client's serialization format is unsupported. */
714
741
  declare const RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
742
+ /** Maximum time to wait for the server's handshake response after opening the socket. */
743
+ declare const HANDSHAKE_TIMEOUT_MS = 10000;
715
744
  /** Builds the WebSocket connection URL for an RpcClientPeer. */
716
745
  type RpcConnectionUrlResolver = (peer: RpcClientPeer) => string;
717
- /** Default connection URL provider — appends `clientId` and `f` query parameters. */
746
+ /** Default connection URL provider — sets `clientId` and `f` query parameters,
747
+ * replacing any existing values to keep the wire format consistent with the
748
+ * peer's actual `serializationFormat`. */
718
749
  declare const defaultConnectionUrlResolver: RpcConnectionUrlResolver;
719
750
  /** Options for RpcPeer.call() — allows custom call types and cancellation. */
720
751
  interface RpcCallOptions {
@@ -736,36 +767,49 @@ interface RemoteHandshake {
736
767
  }
737
768
  /** Base class for RPC peers — handles bidirectional message dispatch. */
738
769
  declare abstract class RpcPeer {
770
+ readonly hub: RpcHub;
739
771
  /** Routing/addressing key — used as key in hub.peers (URL for clients, "server://{uuid}" for servers). */
740
772
  readonly ref: string;
741
773
  /** Auto-generated GUID — used in handshakes only. */
742
774
  readonly id: string;
743
- protected _hub: RpcHub;
744
- protected _connection: RpcConnection | undefined;
745
- readonly outbound: RpcOutboundCallTracker;
746
- readonly inbound: RpcInboundCallTracker;
775
+ readonly outboundCalls: RpcOutboundCallTracker;
776
+ readonly inboundCalls: RpcInboundCallTracker;
747
777
  readonly remoteObjects: RpcRemoteObjectTracker;
748
778
  readonly sharedObjects: RpcSharedObjectTracker;
749
- readonly connected: EventHandlerSet<void>;
750
- readonly disconnected: EventHandlerSet<{
751
- code: number;
752
- reason: string;
753
- }>;
779
+ /** Fired on every state transition with the new value. Initial state is
780
+ * `Disconnected`; `Connecting` is entered when the peer starts an
781
+ * attempt (the reconnect delay is part of this phase); `Connected` is
782
+ * entered only after the handshake has succeeded. */
783
+ readonly connectionStateChanged: EventHandlerSet<RpcConnectionState>;
784
+ protected _connection: RpcConnection | undefined;
785
+ protected _connectionState: RpcConnectionState;
754
786
  protected _pendingSends: RpcOutboundCall[];
755
787
  private _keepAliveTimer;
756
- constructor(ref: string, hub: RpcHub);
757
- get hub(): RpcHub;
788
+ private _keepAliveWatchdog;
789
+ /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
790
+ * for the current connection. Useful for diagnostics. */
791
+ private _lastKeepAliveAt;
792
+ /** Outbound keep-alive send period (per-peer override for tests). */
793
+ keepAlivePeriodMs: number;
794
+ /** Inbound keep-alive silence tolerance (per-peer override for tests). */
795
+ keepAliveTimeoutMs: number;
796
+ protected constructor(hub: RpcHub, ref: string);
758
797
  get connection(): RpcConnection | undefined;
798
+ get connectionState(): RpcConnectionState;
759
799
  get isConnected(): boolean;
800
+ protected _setConnectionState(value: RpcConnectionState): void;
801
+ /** Resolves on the next transition into `Connected` — or immediately if
802
+ * already in that state. */
803
+ whenConnected(): Promise<void>;
760
804
  protected setupConnection(conn: RpcConnection): void;
761
805
  /**
762
806
  * The serialization format used by this peer for outgoing messages.
763
807
  * If not set explicitly, defaults to the resolver's default (JSON /
764
808
  * json5np). Subclasses can pre-assign in their constructor.
765
809
  */
766
- private _format;
767
- get format(): RpcSerializationFormat;
768
- set format(value: RpcSerializationFormat);
810
+ private _serializationFormat;
811
+ get serializationFormat(): RpcSerializationFormat;
812
+ set serializationFormat(value: RpcSerializationFormat);
769
813
  call(method: string, args?: unknown[], options?: RpcCallOptions): RpcOutboundCall;
770
814
  callNoWait(method: string, args?: unknown[]): void;
771
815
  close(): void;
@@ -777,11 +821,22 @@ declare abstract class RpcPeer {
777
821
  protected _onHandshakeReceived(_handshake: RemoteHandshake): void;
778
822
  private _handleMessage;
779
823
  private _handleInbound;
780
- private _startKeepAlive;
781
- private _stopKeepAlive;
824
+ /** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.
825
+ * Called when the peer transitions into `Connected` (after handshake).
826
+ * Each inbound `$sys.KeepAlive` re-arms the watchdog; if it fires, the
827
+ * WebSocket is force-closed so the normal disconnect path runs
828
+ * (`conn.closed` → `connectionState = Disconnected` → UI).
829
+ * Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
830
+ private _armKeepAliveWatchdog;
831
+ /** Stop sending keep-alives and clear the silence watchdog. Called when
832
+ * the peer transitions into `Disconnected`. */
833
+ private _disarmKeepAliveWatchdog;
834
+ /** Called by `RpcSystemCallHandler` when a `$sys.KeepAlive` message
835
+ * arrives — records the timestamp and re-arms the silence watchdog. */
836
+ notifyKeepAliveReceived(): void;
782
837
  }
783
838
  /** Connection state for RpcClientPeer. */
784
- declare const enum RpcPeerConnectionKind {
839
+ declare const enum RpcConnectionState {
785
840
  Disconnected = 0,
786
841
  Connecting = 1,
787
842
  Connected = 2
@@ -789,7 +844,6 @@ declare const enum RpcPeerConnectionKind {
789
844
  /** Client-side RPC peer — initiates WebSocket connection. ref = URL. */
790
845
  declare class RpcClientPeer extends RpcPeer {
791
846
  private _disposed;
792
- private _connectionKind;
793
847
  private _tryIndex;
794
848
  private _handshakeIndex;
795
849
  /** Index from the MOST RECENTLY RECEIVED server handshake — sent back
@@ -803,33 +857,58 @@ declare class RpcClientPeer extends RpcPeer {
803
857
  private _reconnectsAt;
804
858
  /** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */
805
859
  readonly clientId: string;
806
- /**
807
- * Serialization format key sent to the server via `f=` query parameter.
808
- * TS uses plain JSON.stringify (no polymorphic type wrapping), so `json5np`
809
- * (no-polymorphism) is the correct default.
810
- */
811
- readonly serializationFormat: string;
812
860
  /** Builds the WebSocket URL for this peer. Replace to customize URL construction. */
813
861
  connectionUrlResolver: RpcConnectionUrlResolver;
862
+ /** Optional WebSocket factory. When set, `run()` uses this instead of the
863
+ * global `new WebSocket(url)` constructor. Used by tests (to inject mock
864
+ * sockets) and by the Node load-test harness (to attach `Session` /
865
+ * `Cookie` headers to the WS upgrade, which the browser `WebSocket`
866
+ * constructor can't do). Must be set before `start()`. */
867
+ webSocketFactory: ((url: string) => WebSocketLike) | undefined;
868
+ /** Max time to wait for the server's handshake response before aborting
869
+ * the connection attempt. Mirrors .NET's `Hub.Limits.HandshakeTimeout`. */
870
+ handshakeTimeoutMs: number;
814
871
  readonly peerChanged: EventHandlerSet<void>;
815
872
  /** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
816
873
  readonly unsupportedFormat: EventHandlerSet<{
817
874
  reason: string;
818
875
  }>;
819
- readonly reconnectDelayer: RpcClientPeerReconnectDelayer;
820
876
  readonly reconnectsAtChanged: EventHandlerSet<void>;
821
877
  get reconnectsAt(): number;
822
878
  private _setReconnectsAt;
823
- constructor(hub: RpcHub, url: string, serializationFormat?: string);
824
- get connectionKind(): RpcPeerConnectionKind;
879
+ /** The promise returned by the reconnect loop. `undefined` until
880
+ * `start()` is invoked (which happens automatically when
881
+ * `mustStart = true` is passed to the ctor). */
882
+ whenRunning: Promise<void> | undefined;
883
+ /**
884
+ * @param hub RpcHub instance this peer will be tracked by
885
+ * @param url Full WebSocket URL — the serialization format is taken from
886
+ * the `f=` query parameter (e.g. `wss://host/rpc/ws?f=msgpack6`);
887
+ * falls back to the resolver's default when absent. Use
888
+ * {@link RpcPeerRefBuilder.forClient} to compose URLs with a format.
889
+ * @param mustStart Kick off the reconnect loop automatically at the end of
890
+ * construction (default `true`). Pass `false` if you need to
891
+ * tweak `reconnectDelayer`, `handshakeTimeoutMs`, or similar
892
+ * options before the loop starts — then call `peer.start()`
893
+ * manually.
894
+ */
895
+ constructor(hub: RpcHub, url: string, mustStart?: boolean);
825
896
  /** One-shot connection for tests — no reconnection loop, no handshake.
897
+ * @param conn Rpc connection
826
898
  * @param isPeerChanged Whether to treat this as a peer-identity change
827
899
  * (default `true` — no handshake was exchanged, so stage-3 compute calls
828
900
  * self-invalidate and remote/shared objects are disposed). Pass `false`
829
901
  * to simulate a same-peer reconnect in tests. */
830
902
  connectWith(conn: RpcConnection, isPeerChanged?: boolean): void;
831
- /** Start the reconnection loop — runs until disposed. */
832
- run(wsFactory?: (url: string) => WebSocketLike): Promise<void>;
903
+ /** Kick off the reconnect loop. Idempotent — subsequent calls are
904
+ * no-ops; await `peer.whenRunning` if you need the loop's completion
905
+ * promise. Customize `peer.webSocketFactory` first if you need a
906
+ * non-default WebSocket constructor. */
907
+ start(): void;
908
+ /** The reconnect loop itself. Exposed as `protected` so subclasses (and
909
+ * `start()`) can drive it; public callers should use {@link start}. */
910
+ protected run(): Promise<void>;
911
+ private _runImpl;
833
912
  /**
834
913
  * Reconcile outbound calls after reconnection, then flush pending sends.
835
914
  *
@@ -1121,6 +1200,17 @@ declare class RpcStreamSender<T> implements IRpcObject {
1121
1200
  private _forceCloseIterator;
1122
1201
  }
1123
1202
 
1203
+ declare class RpcPeerRefBuilder {
1204
+ /** Build a client peer ref from a WebSocket URL, optionally baking in the
1205
+ * serialization format as a `f=` query parameter.
1206
+ * @param url Base WebSocket URL, e.g. `wss://host/rpc/ws`.
1207
+ * @param serializationFormat Optional format key, e.g. `msgpack6`. When
1208
+ * provided, replaces any existing `f=` in the URL. */
1209
+ static forClient(url: string, serializationFormat?: string): string;
1210
+ /** Build a server peer ref from a peer id — returns `server://{id}`. */
1211
+ static forServer(id: string): string;
1212
+ }
1213
+
1124
1214
  /** Creates a typed RPC client proxy — intercepts method calls and sends them over RPC. */
1125
1215
  declare function createRpcClient<T extends object>(peer: RpcPeer, serviceDef: RpcServiceDef): T;
1126
1216
 
@@ -1154,8 +1244,7 @@ declare class RpcPeerStateMonitor {
1154
1244
  private _lastError;
1155
1245
  private _state;
1156
1246
  private _timer;
1157
- private _onConnected;
1158
- private _onDisconnected;
1247
+ private _onConnectionStateChanged;
1159
1248
  private _onReconnectsAtChanged;
1160
1249
  private _onCancelDelaysChanged;
1161
1250
  constructor(peer: RpcClientPeer);
@@ -1186,4 +1275,4 @@ declare function xxh3_64str(input: string, seed?: bigint): bigint;
1186
1275
  */
1187
1276
  declare function computeMethodHash(methodName: string): number;
1188
1277
 
1189
- export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerConnectionKind, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, 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, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, wireMethodName, xxh3_64, xxh3_64str };
1278
+ export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, HANDSHAKE_TIMEOUT_MS, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, type RpcPeerFactory, RpcPeerRefBuilder, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, 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, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, wireMethodName, xxh3_64, xxh3_64str };
package/dist/index.d.ts CHANGED
@@ -638,6 +638,11 @@ declare class RpcSystemCallSender {
638
638
  ackEnd(conn: RpcConnection, format: RpcSerializationFormat, localId: number, hostId: string): void;
639
639
  }
640
640
 
641
+ /** Factory signature shared by all peer lookup/creation methods. Serialization
642
+ * format is encoded in the URL via `?f=...` (see {@link RpcPeerRefBuilder}),
643
+ * so the factory doesn't need an extra parameter for it. `getClientPeer` /
644
+ * `getServerPeer` cast the result, so the factory returns RpcPeer. */
645
+ type RpcPeerFactory = (hub: RpcHub, ref: string) => RpcPeer;
641
646
  /** Central RPC coordinator — manages peers, services, and configuration. */
642
647
  declare class RpcHub {
643
648
  readonly hubId: string;
@@ -648,15 +653,37 @@ declare class RpcHub {
648
653
  /** Method registry for compact format hash ↔ name resolution.
649
654
  * Created lazily, populated by addService/addClient. */
650
655
  readonly registry: RpcMethodRegistry;
656
+ /** Shared reconnect delayer used by every client peer in this hub. Swap in
657
+ * a custom subclass (e.g. app-level signal-gated) before peers start. */
658
+ reconnectDelayer: RpcClientPeerReconnectDelayer;
659
+ /** URL used by {@link defaultPeer} to resolve / create the default client
660
+ * peer. Must be set before the first {@link defaultPeer} access. */
661
+ defaultPeerUrl: string | undefined;
662
+ /** Factory used by {@link defaultPeer} when the peer needs to be created.
663
+ * If left undefined, `getPeer`'s built-in default (RpcClientPeer with
664
+ * format derived from URL or resolver default) is used. */
665
+ defaultPeerFactory: RpcPeerFactory | undefined;
651
666
  constructor(hubId?: string);
667
+ /** Shortcut for the hub's default client peer. Fails if
668
+ * {@link defaultPeerUrl} is not set. */
669
+ get defaultPeer(): RpcClientPeer;
670
+ /** Register a peer under its ref. If a different peer is already registered
671
+ * at the same ref, close it first — the hub guarantees at most one live
672
+ * peer per ref, and no peer exists outside the hub. */
652
673
  addPeer(peer: RpcPeer): void;
653
674
  removePeer(ref: string): void;
654
- /** Get or create a peer by ref. Server peers have "server://" prefix, everything else is a client peer. */
655
- getPeer(ref: string): RpcPeer;
656
- /** Get or create a client peer for the given URL. */
657
- getClientPeer(ref: string): RpcClientPeer;
658
- /** Get or create a server peer for the given ref. */
659
- getServerPeer(ref: string): RpcServerPeer;
675
+ /** Get or create a peer by ref. If the peer does not exist:
676
+ * - with `factory` provided: it is constructed via `factory(this, ref)`;
677
+ * - otherwise: server peers (ref starts with `server://`) get a plain
678
+ * RpcServerPeer, anything else gets a default-configured RpcClientPeer.
679
+ * The created peer is always registered in this hub. */
680
+ getPeer(ref: string, factory?: RpcPeerFactory): RpcPeer;
681
+ /** Get or create a client peer for the given URL. See {@link getPeer} for
682
+ * the factory semantics. */
683
+ getClientPeer(ref: string, factory?: RpcPeerFactory): RpcClientPeer;
684
+ /** Get or create a server peer for the given ref. See {@link getPeer} for
685
+ * the factory semantics. */
686
+ getServerPeer(ref: string, factory?: RpcPeerFactory): RpcServerPeer;
660
687
  /** Register a service with optional server method wrapping for custom call types. */
661
688
  addService(defOrContract: RpcServiceDef | (AnyConstructor), impl: RpcServiceImpl): void;
662
689
  /** Create a typed client proxy for a service on a remote peer. */
@@ -712,9 +739,13 @@ declare class RpcSharedObjectTracker {
712
739
 
713
740
  /** WebSocket close code sent by the server when the client's serialization format is unsupported. */
714
741
  declare const RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
742
+ /** Maximum time to wait for the server's handshake response after opening the socket. */
743
+ declare const HANDSHAKE_TIMEOUT_MS = 10000;
715
744
  /** Builds the WebSocket connection URL for an RpcClientPeer. */
716
745
  type RpcConnectionUrlResolver = (peer: RpcClientPeer) => string;
717
- /** Default connection URL provider — appends `clientId` and `f` query parameters. */
746
+ /** Default connection URL provider — sets `clientId` and `f` query parameters,
747
+ * replacing any existing values to keep the wire format consistent with the
748
+ * peer's actual `serializationFormat`. */
718
749
  declare const defaultConnectionUrlResolver: RpcConnectionUrlResolver;
719
750
  /** Options for RpcPeer.call() — allows custom call types and cancellation. */
720
751
  interface RpcCallOptions {
@@ -736,36 +767,49 @@ interface RemoteHandshake {
736
767
  }
737
768
  /** Base class for RPC peers — handles bidirectional message dispatch. */
738
769
  declare abstract class RpcPeer {
770
+ readonly hub: RpcHub;
739
771
  /** Routing/addressing key — used as key in hub.peers (URL for clients, "server://{uuid}" for servers). */
740
772
  readonly ref: string;
741
773
  /** Auto-generated GUID — used in handshakes only. */
742
774
  readonly id: string;
743
- protected _hub: RpcHub;
744
- protected _connection: RpcConnection | undefined;
745
- readonly outbound: RpcOutboundCallTracker;
746
- readonly inbound: RpcInboundCallTracker;
775
+ readonly outboundCalls: RpcOutboundCallTracker;
776
+ readonly inboundCalls: RpcInboundCallTracker;
747
777
  readonly remoteObjects: RpcRemoteObjectTracker;
748
778
  readonly sharedObjects: RpcSharedObjectTracker;
749
- readonly connected: EventHandlerSet<void>;
750
- readonly disconnected: EventHandlerSet<{
751
- code: number;
752
- reason: string;
753
- }>;
779
+ /** Fired on every state transition with the new value. Initial state is
780
+ * `Disconnected`; `Connecting` is entered when the peer starts an
781
+ * attempt (the reconnect delay is part of this phase); `Connected` is
782
+ * entered only after the handshake has succeeded. */
783
+ readonly connectionStateChanged: EventHandlerSet<RpcConnectionState>;
784
+ protected _connection: RpcConnection | undefined;
785
+ protected _connectionState: RpcConnectionState;
754
786
  protected _pendingSends: RpcOutboundCall[];
755
787
  private _keepAliveTimer;
756
- constructor(ref: string, hub: RpcHub);
757
- get hub(): RpcHub;
788
+ private _keepAliveWatchdog;
789
+ /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
790
+ * for the current connection. Useful for diagnostics. */
791
+ private _lastKeepAliveAt;
792
+ /** Outbound keep-alive send period (per-peer override for tests). */
793
+ keepAlivePeriodMs: number;
794
+ /** Inbound keep-alive silence tolerance (per-peer override for tests). */
795
+ keepAliveTimeoutMs: number;
796
+ protected constructor(hub: RpcHub, ref: string);
758
797
  get connection(): RpcConnection | undefined;
798
+ get connectionState(): RpcConnectionState;
759
799
  get isConnected(): boolean;
800
+ protected _setConnectionState(value: RpcConnectionState): void;
801
+ /** Resolves on the next transition into `Connected` — or immediately if
802
+ * already in that state. */
803
+ whenConnected(): Promise<void>;
760
804
  protected setupConnection(conn: RpcConnection): void;
761
805
  /**
762
806
  * The serialization format used by this peer for outgoing messages.
763
807
  * If not set explicitly, defaults to the resolver's default (JSON /
764
808
  * json5np). Subclasses can pre-assign in their constructor.
765
809
  */
766
- private _format;
767
- get format(): RpcSerializationFormat;
768
- set format(value: RpcSerializationFormat);
810
+ private _serializationFormat;
811
+ get serializationFormat(): RpcSerializationFormat;
812
+ set serializationFormat(value: RpcSerializationFormat);
769
813
  call(method: string, args?: unknown[], options?: RpcCallOptions): RpcOutboundCall;
770
814
  callNoWait(method: string, args?: unknown[]): void;
771
815
  close(): void;
@@ -777,11 +821,22 @@ declare abstract class RpcPeer {
777
821
  protected _onHandshakeReceived(_handshake: RemoteHandshake): void;
778
822
  private _handleMessage;
779
823
  private _handleInbound;
780
- private _startKeepAlive;
781
- private _stopKeepAlive;
824
+ /** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.
825
+ * Called when the peer transitions into `Connected` (after handshake).
826
+ * Each inbound `$sys.KeepAlive` re-arms the watchdog; if it fires, the
827
+ * WebSocket is force-closed so the normal disconnect path runs
828
+ * (`conn.closed` → `connectionState = Disconnected` → UI).
829
+ * Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */
830
+ private _armKeepAliveWatchdog;
831
+ /** Stop sending keep-alives and clear the silence watchdog. Called when
832
+ * the peer transitions into `Disconnected`. */
833
+ private _disarmKeepAliveWatchdog;
834
+ /** Called by `RpcSystemCallHandler` when a `$sys.KeepAlive` message
835
+ * arrives — records the timestamp and re-arms the silence watchdog. */
836
+ notifyKeepAliveReceived(): void;
782
837
  }
783
838
  /** Connection state for RpcClientPeer. */
784
- declare const enum RpcPeerConnectionKind {
839
+ declare const enum RpcConnectionState {
785
840
  Disconnected = 0,
786
841
  Connecting = 1,
787
842
  Connected = 2
@@ -789,7 +844,6 @@ declare const enum RpcPeerConnectionKind {
789
844
  /** Client-side RPC peer — initiates WebSocket connection. ref = URL. */
790
845
  declare class RpcClientPeer extends RpcPeer {
791
846
  private _disposed;
792
- private _connectionKind;
793
847
  private _tryIndex;
794
848
  private _handshakeIndex;
795
849
  /** Index from the MOST RECENTLY RECEIVED server handshake — sent back
@@ -803,33 +857,58 @@ declare class RpcClientPeer extends RpcPeer {
803
857
  private _reconnectsAt;
804
858
  /** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */
805
859
  readonly clientId: string;
806
- /**
807
- * Serialization format key sent to the server via `f=` query parameter.
808
- * TS uses plain JSON.stringify (no polymorphic type wrapping), so `json5np`
809
- * (no-polymorphism) is the correct default.
810
- */
811
- readonly serializationFormat: string;
812
860
  /** Builds the WebSocket URL for this peer. Replace to customize URL construction. */
813
861
  connectionUrlResolver: RpcConnectionUrlResolver;
862
+ /** Optional WebSocket factory. When set, `run()` uses this instead of the
863
+ * global `new WebSocket(url)` constructor. Used by tests (to inject mock
864
+ * sockets) and by the Node load-test harness (to attach `Session` /
865
+ * `Cookie` headers to the WS upgrade, which the browser `WebSocket`
866
+ * constructor can't do). Must be set before `start()`. */
867
+ webSocketFactory: ((url: string) => WebSocketLike) | undefined;
868
+ /** Max time to wait for the server's handshake response before aborting
869
+ * the connection attempt. Mirrors .NET's `Hub.Limits.HandshakeTimeout`. */
870
+ handshakeTimeoutMs: number;
814
871
  readonly peerChanged: EventHandlerSet<void>;
815
872
  /** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
816
873
  readonly unsupportedFormat: EventHandlerSet<{
817
874
  reason: string;
818
875
  }>;
819
- readonly reconnectDelayer: RpcClientPeerReconnectDelayer;
820
876
  readonly reconnectsAtChanged: EventHandlerSet<void>;
821
877
  get reconnectsAt(): number;
822
878
  private _setReconnectsAt;
823
- constructor(hub: RpcHub, url: string, serializationFormat?: string);
824
- get connectionKind(): RpcPeerConnectionKind;
879
+ /** The promise returned by the reconnect loop. `undefined` until
880
+ * `start()` is invoked (which happens automatically when
881
+ * `mustStart = true` is passed to the ctor). */
882
+ whenRunning: Promise<void> | undefined;
883
+ /**
884
+ * @param hub RpcHub instance this peer will be tracked by
885
+ * @param url Full WebSocket URL — the serialization format is taken from
886
+ * the `f=` query parameter (e.g. `wss://host/rpc/ws?f=msgpack6`);
887
+ * falls back to the resolver's default when absent. Use
888
+ * {@link RpcPeerRefBuilder.forClient} to compose URLs with a format.
889
+ * @param mustStart Kick off the reconnect loop automatically at the end of
890
+ * construction (default `true`). Pass `false` if you need to
891
+ * tweak `reconnectDelayer`, `handshakeTimeoutMs`, or similar
892
+ * options before the loop starts — then call `peer.start()`
893
+ * manually.
894
+ */
895
+ constructor(hub: RpcHub, url: string, mustStart?: boolean);
825
896
  /** One-shot connection for tests — no reconnection loop, no handshake.
897
+ * @param conn Rpc connection
826
898
  * @param isPeerChanged Whether to treat this as a peer-identity change
827
899
  * (default `true` — no handshake was exchanged, so stage-3 compute calls
828
900
  * self-invalidate and remote/shared objects are disposed). Pass `false`
829
901
  * to simulate a same-peer reconnect in tests. */
830
902
  connectWith(conn: RpcConnection, isPeerChanged?: boolean): void;
831
- /** Start the reconnection loop — runs until disposed. */
832
- run(wsFactory?: (url: string) => WebSocketLike): Promise<void>;
903
+ /** Kick off the reconnect loop. Idempotent — subsequent calls are
904
+ * no-ops; await `peer.whenRunning` if you need the loop's completion
905
+ * promise. Customize `peer.webSocketFactory` first if you need a
906
+ * non-default WebSocket constructor. */
907
+ start(): void;
908
+ /** The reconnect loop itself. Exposed as `protected` so subclasses (and
909
+ * `start()`) can drive it; public callers should use {@link start}. */
910
+ protected run(): Promise<void>;
911
+ private _runImpl;
833
912
  /**
834
913
  * Reconcile outbound calls after reconnection, then flush pending sends.
835
914
  *
@@ -1121,6 +1200,17 @@ declare class RpcStreamSender<T> implements IRpcObject {
1121
1200
  private _forceCloseIterator;
1122
1201
  }
1123
1202
 
1203
+ declare class RpcPeerRefBuilder {
1204
+ /** Build a client peer ref from a WebSocket URL, optionally baking in the
1205
+ * serialization format as a `f=` query parameter.
1206
+ * @param url Base WebSocket URL, e.g. `wss://host/rpc/ws`.
1207
+ * @param serializationFormat Optional format key, e.g. `msgpack6`. When
1208
+ * provided, replaces any existing `f=` in the URL. */
1209
+ static forClient(url: string, serializationFormat?: string): string;
1210
+ /** Build a server peer ref from a peer id — returns `server://{id}`. */
1211
+ static forServer(id: string): string;
1212
+ }
1213
+
1124
1214
  /** Creates a typed RPC client proxy — intercepts method calls and sends them over RPC. */
1125
1215
  declare function createRpcClient<T extends object>(peer: RpcPeer, serviceDef: RpcServiceDef): T;
1126
1216
 
@@ -1154,8 +1244,7 @@ declare class RpcPeerStateMonitor {
1154
1244
  private _lastError;
1155
1245
  private _state;
1156
1246
  private _timer;
1157
- private _onConnected;
1158
- private _onDisconnected;
1247
+ private _onConnectionStateChanged;
1159
1248
  private _onReconnectsAtChanged;
1160
1249
  private _onCancelDelaysChanged;
1161
1250
  constructor(peer: RpcClientPeer);
@@ -1186,4 +1275,4 @@ declare function xxh3_64str(input: string, seed?: bigint): bigint;
1186
1275
  */
1187
1276
  declare function computeMethodHash(methodName: string): number;
1188
1277
 
1189
- export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerConnectionKind, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, 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, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, wireMethodName, xxh3_64, xxh3_64str };
1278
+ export { ARG_DELIMITER, ENVELOPE_DELIMITER, FRAME_DELIMITER, HANDSHAKE_TIMEOUT_MS, type IRpcObject, IncreasingSeqCompressor, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallStage, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, RpcConnectionState, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, type RpcLogScope, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, type RpcPeerFactory, RpcPeerRefBuilder, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, 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, createBinaryEncoder, createMessageChannelPair, createRpcClient, defaultBinaryDecoder, defaultBinaryEncoder, defaultConnectionUrlResolver, defineRpcService, deserializeBinaryMessage, deserializeCompactBinaryMessage, deserializeMessage, getLogs, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, wireMethodName, xxh3_64, xxh3_64str };