@actuallab/rpc 12.1.115 → 12.2.21

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
@@ -1,3 +1,4 @@
1
+ import { Encoder, Decoder } from '@msgpack/msgpack';
1
2
  import { EventHandlerSet, PromiseSource, RetryDelayer } from '@actuallab/core';
2
3
 
3
4
  /** Identifies the type of an RPC call on the wire. */
@@ -30,6 +31,29 @@ declare const ENVELOPE_DELIMITER = "\n";
30
31
  declare const ARG_DELIMITER = "\u001F";
31
32
  declare const FRAME_DELIMITER = "\n\u001E";
32
33
 
34
+ /**
35
+ * Registry mapping RPC method wire names to/from their 4-byte XXH3 hashes.
36
+ * Hashes match .NET's RpcMethodRef.ComputeHashCode.
37
+ * Hashes are computed lazily on first access and cached.
38
+ */
39
+ declare class RpcMethodRegistry {
40
+ private _nameToHash;
41
+ private _hashToName;
42
+ /** Register a method name and compute its hash. */
43
+ register(methodName: string): void;
44
+ /** Register all wire method names from a service definition. */
45
+ registerService(serviceName: string, methods: ReadonlyMap<string, {
46
+ name: string;
47
+ wireArgCount: number;
48
+ }>): void;
49
+ /** Get the hash for a method name. Computes on demand if not registered. */
50
+ getHash(methodName: string): number;
51
+ /** Get the method name for a hash. Returns undefined if not registered. */
52
+ getName(hash: number): string | undefined;
53
+ /** Get the hash for a method name, throwing if not registered. */
54
+ requireHash(methodName: string): number;
55
+ }
56
+
33
57
  /** Serializes an RpcMessage + args into the json5 wire format. */
34
58
  declare function serializeMessage(message: RpcMessage, args?: unknown[]): string;
35
59
  /** Serializes multiple messages into a single WebSocket frame. */
@@ -41,11 +65,189 @@ declare function deserializeMessage(raw: string): {
41
65
  message: RpcMessage;
42
66
  args: unknown[];
43
67
  };
68
+ declare const defaultBinaryEncoder: Encoder<undefined>;
69
+ declare const defaultBinaryDecoder: Decoder<undefined>;
70
+ /** Exposed so connection code can instantiate a matching Encoder
71
+ * with the same initial buffer size without needing to reach into
72
+ * this module's private constants. */
73
+ declare function createBinaryEncoder(): Encoder;
74
+ /**
75
+ * Serializes an RpcMessage + args into V5 binary format (msgpack6np).
76
+ *
77
+ * Layout (NO frame-level size prefix; V5 with PersistsMessageSize=false):
78
+ * envelope + argData
79
+ * Envelope:
80
+ * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]
81
+ * [LVarSpan methodRef] [4-byte LE argLen]
82
+ * ArgData: concatenated MessagePack-encoded arguments
83
+ *
84
+ * Single-allocation build: pre-encodes args with the supplied reusable
85
+ * `Encoder`, computes the total envelope size, then writes everything
86
+ * into one `Uint8Array`. Eliminates the previous build's intermediate
87
+ * `headerParts` number array + four `Uint8Array` concats per call,
88
+ * which showed up as ~2% self time in the 300-pull profile and
89
+ * pulled the msgpack `resizeBuffer` + `Encoder` constructor along
90
+ * with it for another ~6%.
91
+ *
92
+ * NOTE: `@msgpack/msgpack` `Encoder.encode()` already returns a fresh
93
+ * copy of the internal buffer (there's a separate `encodeSharedRef()`
94
+ * for zero-copy access), so we can push the results straight into
95
+ * `argBufs` without an extra slice. The benefit of reusing the Encoder
96
+ * is that its internal write buffer stays at its high-water mark —
97
+ * once the first ~11 KB frame has grown it to the full size, no
98
+ * subsequent `resizeBuffer` calls are needed.
99
+ */
100
+ declare function serializeBinaryMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder): Uint8Array;
101
+ /**
102
+ * Deserializes a single V5 binary message starting at `offset`.
103
+ * Returns parsed message, args, and number of bytes consumed.
104
+ *
105
+ * Accepts an optional `Decoder` for reuse across calls on the same
106
+ * connection — avoids constructing a new `@msgpack/msgpack.Decoder`
107
+ * (and its internal resolver/table state) on every inbound message.
108
+ */
109
+ declare function deserializeBinaryMessage(data: Uint8Array, offset: number, decoder?: Decoder): {
110
+ message: RpcMessage;
111
+ args: unknown[];
112
+ bytesRead: number;
113
+ };
114
+ /**
115
+ * Splits a binary WebSocket frame into individual deserialized messages.
116
+ * V5 envelopes are self-delimiting (header + method + argLen + argData), so
117
+ * multiple envelopes concatenated in a single WebSocket frame can be decoded
118
+ * by iterating with the bytesRead returned from deserializeBinaryMessage.
119
+ *
120
+ * This must stay symmetric with serializeBinaryFrame() (which concatenates
121
+ * multiple envelopes). The .NET server batches outbound RPC messages into a
122
+ * single WebSocket frame via WebSocketChannel/WriteDelayer, so any assumption
123
+ * that a frame is a single envelope silently drops every trailing message.
124
+ *
125
+ * Accepts an optional `Decoder` for reuse across calls on the same
126
+ * connection — forwarded to `deserializeBinaryMessage`.
127
+ */
128
+ declare function splitBinaryFrame(frame: Uint8Array, decoder?: Decoder): Array<{
129
+ message: RpcMessage;
130
+ args: unknown[];
131
+ }>;
132
+ /**
133
+ * Serializes multiple binary messages into a single WebSocket frame.
134
+ * Simply concatenates the already size-prefixed messages.
135
+ */
136
+ declare function serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
137
+
138
+ /**
139
+ * Serializes an RpcMessage + args into V5Compact binary format.
140
+ * Same as V5 except the method is encoded as a 4-byte LE hash
141
+ * instead of an LVar-prefixed UTF-8 string.
142
+ *
143
+ * Layout:
144
+ * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]
145
+ * [4-byte LE methodHash] [4-byte LE argLen]
146
+ * [argData]
147
+ */
148
+ declare function serializeCompactBinaryMessage(message: RpcMessage, args?: unknown[], registry?: RpcMethodRegistry, encoder?: Encoder): Uint8Array;
149
+ /**
150
+ * Deserializes a single V5Compact binary message starting at `offset`.
151
+ * Reads a 4-byte LE method hash and resolves it via the registry.
152
+ */
153
+ declare function deserializeCompactBinaryMessage(data: Uint8Array, offset: number, registry?: RpcMethodRegistry, decoder?: Decoder): {
154
+ message: RpcMessage;
155
+ args: unknown[];
156
+ bytesRead: number;
157
+ };
158
+ /**
159
+ * Splits a compact binary frame into individual deserialized messages.
160
+ */
161
+ declare function splitCompactBinaryFrame(frame: Uint8Array, registry?: RpcMethodRegistry, decoder?: Decoder): Array<{
162
+ message: RpcMessage;
163
+ args: unknown[];
164
+ }>;
165
+
166
+ /** Parsed inbound message — result of deserialization. */
167
+ interface RpcDeserializedMessage {
168
+ message: RpcMessage;
169
+ args: unknown[];
170
+ }
171
+ /** Wire data produced by serialization — either text or binary. */
172
+ type RpcWireData = string | Uint8Array;
173
+ /**
174
+ * Base class for RPC serialization formats.
175
+ * Instances are immutable singletons — one per format key.
176
+ * State (like method registries) is passed via parameters.
177
+ */
178
+ declare abstract class RpcSerializationFormat {
179
+ abstract readonly key: string;
180
+ abstract readonly isBinary: boolean;
181
+ /** Whether this format uses compact method hashes (needs RpcMethodRegistry). */
182
+ abstract readonly isCompact: boolean;
183
+ /** Serialize an outbound message. */
184
+ abstract serializeMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder, registry?: RpcMethodRegistry): RpcWireData;
185
+ /** Deserialize a single text message (only called for text formats). */
186
+ abstract deserializeTextMessage(raw: string): RpcDeserializedMessage;
187
+ /** Split + deserialize a binary frame (only called for binary formats). */
188
+ abstract splitBinaryFrame(frame: Uint8Array, decoder?: Decoder, registry?: RpcMethodRegistry): RpcDeserializedMessage[];
189
+ /** Concatenate multiple serialized binary messages into one frame. */
190
+ abstract serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
191
+ /** Create a reusable binary encoder for this format. Returns undefined for text formats. */
192
+ createEncoder(): Encoder | undefined;
193
+ /** Create a reusable binary decoder for this format. Returns undefined for text formats. */
194
+ createDecoder(): Decoder | undefined;
195
+ private static _all;
196
+ static register(format: RpcSerializationFormat): void;
197
+ static get(key: string): RpcSerializationFormat;
198
+ static tryGet(key: string): RpcSerializationFormat | undefined;
199
+ }
200
+ /**
201
+ * JSON text format — "json5np" (System.Text.Json, no polymorphism).
202
+ */
203
+ declare class RpcJsonSerializationFormat extends RpcSerializationFormat {
204
+ readonly key: string;
205
+ readonly isBinary = false;
206
+ readonly isCompact = false;
207
+ constructor(key: string);
208
+ serializeMessage(message: RpcMessage, args?: unknown[]): string;
209
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
210
+ splitBinaryFrame(): RpcDeserializedMessage[];
211
+ serializeBinaryFrame(): Uint8Array;
212
+ }
213
+ /**
214
+ * MessagePack binary format — "msgpack6" / "mempack6" (V5 wire format).
215
+ */
216
+ declare class RpcMessagePackSerializationFormat extends RpcSerializationFormat {
217
+ readonly key: string;
218
+ readonly isBinary = true;
219
+ readonly isCompact = false;
220
+ constructor(key: string);
221
+ serializeMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder): Uint8Array;
222
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
223
+ splitBinaryFrame(frame: Uint8Array, decoder?: Decoder): RpcDeserializedMessage[];
224
+ serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
225
+ createEncoder(): Encoder;
226
+ createDecoder(): Decoder;
227
+ }
228
+ /**
229
+ * MessagePack compact binary format — "msgpack6c" / "mempack6c" (V5Compact).
230
+ * Uses 4-byte method hash instead of full method name.
231
+ * Registry is passed via parameters — format instance is immutable.
232
+ */
233
+ declare class RpcMessagePackCompactSerializationFormat extends RpcSerializationFormat {
234
+ readonly key: string;
235
+ readonly isBinary = true;
236
+ readonly isCompact = true;
237
+ constructor(key: string);
238
+ serializeMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder, registry?: RpcMethodRegistry): Uint8Array;
239
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
240
+ splitBinaryFrame(frame: Uint8Array, decoder?: Decoder, registry?: RpcMethodRegistry): RpcDeserializedMessage[];
241
+ serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
242
+ createEncoder(): Encoder;
243
+ createDecoder(): Decoder;
244
+ }
44
245
 
45
246
  /** Abstract WebSocket interface — works with both browser WebSocket and Node.js ws. */
46
247
  interface WebSocketLike {
47
248
  readonly readyState: number;
48
- send(data: string): void;
249
+ binaryType?: string;
250
+ send(data: string | ArrayBufferLike | Uint8Array | ArrayBufferView): void;
49
251
  close(code?: number, reason?: string): void;
50
252
  onopen: ((ev: unknown) => void) | null;
51
253
  onmessage: ((ev: {
@@ -63,35 +265,72 @@ declare const WebSocketState: {
63
265
  readonly CLOSING: 2;
64
266
  readonly CLOSED: 3;
65
267
  };
268
+ /** Received message — either text (string) or binary (already parsed). */
269
+ type RpcReceivedMessage = {
270
+ kind: 'text';
271
+ raw: string;
272
+ } | {
273
+ kind: 'binary';
274
+ message: RpcMessage;
275
+ args: unknown[];
276
+ };
66
277
  /** Abstract RPC connection — transport-agnostic interface for sending/receiving messages. */
67
278
  interface RpcConnection {
68
279
  readonly isOpen: boolean;
280
+ readonly binaryMode: boolean;
69
281
  readonly whenConnected: Promise<void>;
70
- readonly messageReceived: EventHandlerSet<string>;
282
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
71
283
  readonly closed: EventHandlerSet<{
72
284
  code: number;
73
285
  reason: string;
74
286
  }>;
287
+ /** Optional reusable msgpack encoder for outbound binary messages.
288
+ * Callers passing this to `serializeBinaryMessage` skip the per-call
289
+ * Encoder construction + resizeBuffer growth. Implementations that
290
+ * don't support binary serialization can leave it undefined. */
291
+ readonly encoder?: Encoder;
75
292
  send(serializedMessage: string): void;
293
+ sendBinary(data: Uint8Array): void;
76
294
  close(code?: number, reason?: string): void;
77
295
  }
78
- /** WebSocket-based RpcConnection — handles frame splitting and message queueing. */
296
+ /** WebSocket-based RpcConnection — handles frame splitting, binary/text modes, and message queueing. */
79
297
  declare class RpcWebSocketConnection implements RpcConnection {
80
298
  private _ws;
81
299
  private _sendBuffer;
82
300
  private _connected;
83
- readonly messageReceived: EventHandlerSet<string>;
301
+ /** Per-connection msgpack encoder — reused across outbound messages to
302
+ * amortise construction cost and to keep the internal write buffer at
303
+ * its largest observed size (avoids repeated `resizeBuffer` growth on
304
+ * large video frames). See serializeBinaryMessage() for details. */
305
+ private _encoder;
306
+ /** Per-connection msgpack decoder — reused across inbound messages
307
+ * rather than reconstructing one for every `decodeMulti` call. */
308
+ private _decoder;
309
+ /** Exposed for callers who need to encode outbound binary messages
310
+ * through this connection (e.g. `serializeBinaryMessage`). Safe to
311
+ * pass to synchronous encode calls on the same event-loop turn. */
312
+ get encoder(): Encoder;
313
+ get decoder(): Decoder;
314
+ private _format;
315
+ private _methodRegistry;
316
+ readonly binaryMode: boolean;
317
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
84
318
  readonly closed: EventHandlerSet<{
85
319
  code: number;
86
320
  reason: string;
87
321
  }>;
88
322
  readonly error: EventHandlerSet<unknown>;
89
- constructor(ws: WebSocketLike);
323
+ constructor(ws: WebSocketLike, binaryMode?: boolean, format?: RpcSerializationFormat, methodRegistry?: RpcMethodRegistry);
90
324
  get isOpen(): boolean;
91
325
  get whenConnected(): Promise<void>;
92
326
  send(serializedMessage: string): void;
93
- sendBatch(messages: string[]): void;
327
+ sendBinary(data: Uint8Array): void;
328
+ sendTextBatch(messages: string[]): void;
329
+ sendBinaryBatch(messages: Uint8Array[]): void;
330
+ /** Split a binary frame using the format (compact-aware) or default splitter. */
331
+ private _splitBinary;
94
332
  close(code?: number, reason?: string): void;
333
+ private _sendRaw;
95
334
  private _flush;
96
335
  }
97
336
 
@@ -99,7 +338,8 @@ declare class RpcWebSocketConnection implements RpcConnection {
99
338
  declare class RpcMessageChannelConnection implements RpcConnection {
100
339
  private _port;
101
340
  private _open;
102
- readonly messageReceived: EventHandlerSet<string>;
341
+ readonly binaryMode = false;
342
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
103
343
  readonly closed: EventHandlerSet<{
104
344
  code: number;
105
345
  reason: string;
@@ -108,10 +348,14 @@ declare class RpcMessageChannelConnection implements RpcConnection {
108
348
  constructor(port: MessagePort);
109
349
  get isOpen(): boolean;
110
350
  send(serializedMessage: string): void;
351
+ sendBinary(data: Uint8Array): void;
111
352
  close(code?: number, reason?: string): void;
112
353
  }
113
354
  /** Creates a pair of connected RpcMessageChannelConnections for testing. */
114
- declare function createMessageChannelPair(): [RpcMessageChannelConnection, RpcMessageChannelConnection];
355
+ declare function createMessageChannelPair(): [
356
+ RpcMessageChannelConnection,
357
+ RpcMessageChannelConnection
358
+ ];
115
359
 
116
360
  /** Describes a single method on an RPC service. */
117
361
  interface RpcMethodDef {
@@ -153,8 +397,8 @@ declare class RpcOutboundCall {
153
397
  /** Whether to remove this call from the tracker on $sys.Ok. Default: true.
154
398
  * Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */
155
399
  readonly removeOnOk: boolean;
156
- /** The serialized wire message — stored for re-sending on reconnect. */
157
- serializedMessage: string;
400
+ /** The serialized wire data — stored for re-sending on reconnect. */
401
+ serializedWireData: string | Uint8Array;
158
402
  constructor(callId: number, method: string);
159
403
  /** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */
160
404
  onDisconnect(): void;
@@ -213,20 +457,49 @@ declare class RpcServiceHost {
213
457
  getMethodDef(wireMethod: string): RpcMethodDef | undefined;
214
458
  }
215
459
 
216
- /** Sends system RPC messages — like .NET's RpcSystemCallSender. */
460
+ /**
461
+ * Sends system RPC messages — like .NET's RpcSystemCallSender.
462
+ *
463
+ * Hot-path allocation notes: `item`, `batch`, `ack`, and `end` are called
464
+ * once per stream frame (or once per ack window). Naively building a fresh
465
+ * `{ Method, RelatedId }` envelope object plus a fresh two-element arg array
466
+ * on every call showed up as ~2-4% self time + non-trivial GC pressure in
467
+ * the 300-pull video-load-test profile.
468
+ *
469
+ * The sender itself is a per-Hub singleton and `_send`/format.serializeMessage
470
+ * are fully synchronous (no awaits, no re-entrancy on a single-threaded event
471
+ * loop), so we reuse a small pool of preallocated envelope + arg-array slots
472
+ * per hot method. Mutate fields just before the send call; the encoder reads
473
+ * them synchronously and produces a detached copy, after which the slot is
474
+ * free to be mutated again on the next call.
475
+ */
217
476
  declare class RpcSystemCallSender {
218
- handshake(conn: RpcConnection, peerId: string, hubId: string, index: number): void;
219
- ok(conn: RpcConnection, relatedId: number, result: unknown): void;
220
- error(conn: RpcConnection, relatedId: number, error: unknown): void;
221
- cancel(conn: RpcConnection, relatedId: number): void;
222
- keepAlive(conn: RpcConnection, activeCallIds: number[]): void;
223
- item(conn: RpcConnection, localId: number, index: number, item: unknown): void;
224
- batch(conn: RpcConnection, localId: number, index: number, items: unknown[]): void;
225
- end(conn: RpcConnection, localId: number, index: number, error?: {
477
+ private readonly _itemEnv;
478
+ private readonly _batchEnv;
479
+ private readonly _endEnv;
480
+ private readonly _ackEnv;
481
+ private readonly _ackEndEnv;
482
+ private readonly _itemArgs;
483
+ private readonly _batchArgs;
484
+ private readonly _endArgs;
485
+ private readonly _ackArgs;
486
+ private readonly _ackEndArgs;
487
+ private static readonly NO_ERROR;
488
+ registry?: RpcMethodRegistry;
489
+ private _send;
490
+ handshake(conn: RpcConnection, format: RpcSerializationFormat, peerId: string, hubId: string, index: number): void;
491
+ ok(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number, result: unknown): void;
492
+ error(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number, error: unknown): void;
493
+ cancel(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number): void;
494
+ keepAlive(conn: RpcConnection, format: RpcSerializationFormat, activeCallIds: number[]): void;
495
+ item(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, item: unknown): void;
496
+ batch(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, items: unknown[]): void;
497
+ end(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, error?: {
498
+ TypeRef: string;
226
499
  Message: string;
227
- } | null): void;
228
- ack(conn: RpcConnection, localId: number, nextIndex: number, hostId: string): void;
229
- ackEnd(conn: RpcConnection, localId: number, hostId: string): void;
500
+ }): void;
501
+ ack(conn: RpcConnection, format: RpcSerializationFormat, localId: number, nextIndex: number, hostId: string): void;
502
+ ackEnd(conn: RpcConnection, format: RpcSerializationFormat, localId: number, hostId: string): void;
230
503
  }
231
504
 
232
505
  /** Central RPC coordinator — manages peers, services, and configuration. */
@@ -236,6 +509,9 @@ declare class RpcHub {
236
509
  readonly serviceHost: RpcServiceHost;
237
510
  readonly systemCallSender: RpcSystemCallSender;
238
511
  systemCallHandler: RpcSystemCallHandler;
512
+ /** Method registry for compact format hash ↔ name resolution.
513
+ * Created lazily, populated by addService/addClient. */
514
+ readonly registry: RpcMethodRegistry;
239
515
  constructor(hubId?: string);
240
516
  addPeer(peer: RpcPeer): void;
241
517
  removePeer(ref: string): void;
@@ -281,6 +557,7 @@ declare class RpcRemoteObjectTracker {
281
557
  private _objects;
282
558
  register(obj: IRpcObject): void;
283
559
  get(localId: number): IRpcObject | undefined;
560
+ keys(): IterableIterator<number>;
284
561
  unregister(obj: IRpcObject): void;
285
562
  disconnectAll(): void;
286
563
  reconnectAll(): void;
@@ -292,6 +569,7 @@ declare class RpcSharedObjectTracker {
292
569
  nextId(): number;
293
570
  register(obj: IRpcObject): void;
294
571
  get(localId: number): IRpcObject | undefined;
572
+ keys(): IterableIterator<number>;
295
573
  unregister(obj: IRpcObject): void;
296
574
  disconnectAll(): void;
297
575
  }
@@ -348,11 +626,15 @@ declare abstract class RpcPeer {
348
626
  get connection(): RpcConnection | undefined;
349
627
  get isConnected(): boolean;
350
628
  protected setupConnection(conn: RpcConnection): void;
629
+ /** The serialization format used by this peer. Override in subclasses. */
630
+ get format(): RpcSerializationFormat;
351
631
  call(method: string, args?: unknown[], options?: RpcCallOptions): RpcOutboundCall;
352
632
  callNoWait(method: string, args?: unknown[]): void;
353
633
  close(): void;
354
634
  /** Send any messages buffered while disconnected. Call after connection + handshake are ready. */
355
635
  protected _flushPendingSends(): void;
636
+ /** Send pre-serialized wire data through the current connection. */
637
+ protected _sendWireData(data: string | Uint8Array): void;
356
638
  /** Override in subclasses to handle the remote peer's handshake response. */
357
639
  protected _onHandshakeReceived(_handshake: RemoteHandshake): void;
358
640
  private _handleMessage;
@@ -394,7 +676,9 @@ declare class RpcClientPeer extends RpcPeer {
394
676
  readonly reconnectsAtChanged: EventHandlerSet<void>;
395
677
  get reconnectsAt(): number;
396
678
  private _setReconnectsAt;
679
+ private _format;
397
680
  constructor(hub: RpcHub, url: string, serializationFormat?: string);
681
+ get format(): RpcSerializationFormat;
398
682
  get connectionKind(): RpcPeerConnectionKind;
399
683
  /** One-shot connection for tests — no reconnection loop, no handshake. */
400
684
  connectWith(conn: RpcConnection): void;
@@ -431,6 +715,7 @@ interface RpcStreamRef {
431
715
  readonly ackPeriod: number;
432
716
  readonly ackAdvance: number;
433
717
  readonly allowReconnect: boolean;
718
+ readonly isRealTime: boolean;
434
719
  }
435
720
  /**
436
721
  * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{ackAdvance}".
@@ -477,6 +762,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
477
762
  dispose(): void;
478
763
  private _notifyConsumer;
479
764
  private _maybeSendAck;
765
+ private static readonly _emptyGuid;
480
766
  private _sendAck;
481
767
  private _sendAckEnd;
482
768
  }
@@ -504,17 +790,21 @@ declare class RpcStreamSender<T> implements IRpcObject {
504
790
  readonly id: RpcObjectId;
505
791
  readonly kind: 0;
506
792
  readonly allowReconnect: boolean;
793
+ readonly isRealTime: boolean;
794
+ readonly canSkipTo: (item: T) => boolean;
507
795
  readonly peer: RpcPeer;
508
796
  readonly ackPeriod: number;
509
797
  readonly ackAdvance: number;
510
798
  private _nextIndex;
511
799
  private _ended;
512
800
  private _started;
513
- constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean);
801
+ private _lastAckedIndex;
802
+ private _ackWaiting;
803
+ constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean);
514
804
  /** Returns the stream reference string for the $sys.Ok response. */
515
805
  toRef(): string;
516
806
  /** Called by system call handler when $sys.Ack is received from the client. */
517
- onAck(_nextIndex: number, _hostId: string): void;
807
+ onAck(nextIndex: number, _hostId: string): void;
518
808
  /** Called by system call handler when $sys.AckEnd is received from the client. */
519
809
  onAckEnd(_hostId: string): void;
520
810
  /** Send a single item to the client. */
@@ -526,8 +816,15 @@ declare class RpcStreamSender<T> implements IRpcObject {
526
816
  /**
527
817
  * Consume an AsyncIterable and send all items to the client.
528
818
  * Waits for the client's initial Ack before starting to pump items.
819
+ *
820
+ * When `isRealTime` is true, applies flow control based on `ackAdvance`:
821
+ * if the sender is `ackAdvance` items ahead of the last ACK, it enters
822
+ * skip mode — draining the source and discarding items until it finds
823
+ * one where `canSkipTo` returns true.
529
824
  */
530
825
  writeFrom(source: AsyncIterable<T>): Promise<void>;
826
+ /** Wait until _nextIndex < _lastAckedIndex + ackAdvance. */
827
+ private _waitForAckBudget;
531
828
  reconnect(): void;
532
829
  disconnect(): void;
533
830
  }
@@ -596,4 +893,24 @@ declare class RpcPeerStateMonitor {
596
893
  private _scheduleRecompute;
597
894
  }
598
895
 
599
- export { ARG_DELIMITER, DEFAULT_SERIALIZATION_FORMAT, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, type RpcConnectionUrlResolver, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, type RpcMessage, RpcMessageChannelConnection, type RpcMethodDef, type RpcMethodDefInput, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerConnectionKind, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, RpcRemoteObjectTracker, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamRef, RpcStreamSender, RpcSystemCallHandler, RpcSystemCallSender, RpcSystemCalls, RpcType, RpcWebSocketConnection, type ServiceMeta, type WebSocketLike, type WebSocketServer, WebSocketState, createMessageChannelPair, createRpcClient, defaultConnectionUrlResolver, defineRpcService, deserializeMessage, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeFrame, serializeMessage, splitFrame, wireMethodName };
896
+ /**
897
+ * Compute XXH3-64 hash of a byte array. Returns a BigInt (unsigned 64-bit).
898
+ * Pure TypeScript, synchronous, supports any input length.
899
+ */
900
+ declare function xxh3_64(input: Uint8Array, seed?: bigint): bigint;
901
+ /**
902
+ * Compute XXH3-64 hash of a UTF-8 string. Returns a BigInt (unsigned 64-bit).
903
+ */
904
+ declare function xxh3_64str(input: string, seed?: bigint): bigint;
905
+ /**
906
+ * Compute the RPC method hash matching .NET's RpcMethodRef.ComputeHashCode.
907
+ * Returns a 32-bit signed integer (lower 32 bits of XXH3-64 of [prefix + utf8Name]).
908
+ *
909
+ * The prefix is uint32 LE of (67211 * utf8Name.length), which ensures methods
910
+ * with different name lengths but similar text produce different hashes.
911
+ *
912
+ * Computed on demand — caller should cache the result per method name.
913
+ */
914
+ declare function computeMethodHash(methodName: string): number;
915
+
916
+ export { ARG_DELIMITER, DEFAULT_SERIALIZATION_FORMAT, ENVELOPE_DELIMITER, FRAME_DELIMITER, type IRpcObject, type MethodMeta, RPC_CLOSE_CODE_UNSUPPORTED_FORMAT, type RemoteHandshake, type RpcCallOptions, RpcCallTypeId, RpcClientPeer, RpcClientPeerReconnectDelayer, type RpcConnection, type RpcConnectionUrlResolver, type RpcDeserializedMessage, type RpcDispatchContext, RpcHub, RpcInboundCall, RpcInboundCallTracker, RpcJsonSerializationFormat, type RpcMessage, RpcMessageChannelConnection, RpcMessagePackCompactSerializationFormat, RpcMessagePackSerializationFormat, type RpcMethodDef, type RpcMethodDefInput, RpcMethodRegistry, type RpcObjectId, RpcObjectKind, RpcOutboundCall, RpcOutboundCallTracker, RpcPeer, RpcPeerConnectionKind, type RpcPeerState, RpcPeerStateKind, RpcPeerStateMonitor, type RpcReceivedMessage, RpcRemoteObjectTracker, RpcSerializationFormat, RpcServerPeer, type RpcServiceDef, RpcServiceHost, type RpcServiceImpl, RpcSharedObjectTracker, RpcStream, type RpcStreamRef, RpcStreamSender, 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, getMethodsMeta, getServiceMeta, getStateDescription, isConnected, likelyConnected, parseStreamRef, resolveStreamRefs, rpcMethod, rpcService, serializeBinaryFrame, serializeBinaryMessage, serializeCompactBinaryMessage, serializeFrame, serializeMessage, splitBinaryFrame, splitCompactBinaryFrame, splitFrame, wireMethodName, xxh3_64, xxh3_64str };