@actuallab/rpc 12.1.115 → 12.2.23

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.ts 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,212 @@ 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
+ static SystemJsonV5: RpcSerializationFormat;
196
+ static SystemJsonV5NP: RpcSerializationFormat;
197
+ static NewtonsoftJsonV5: RpcSerializationFormat;
198
+ static NewtonsoftJsonV5NP: RpcSerializationFormat;
199
+ static MessagePackV6: RpcSerializationFormat;
200
+ static MemoryPackV6: RpcSerializationFormat;
201
+ static MessagePackV6C: RpcSerializationFormat;
202
+ static MemoryPackV6C: RpcSerializationFormat;
203
+ static All: readonly RpcSerializationFormat[];
204
+ /** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */
205
+ static get(key: string): RpcSerializationFormat;
206
+ /** Convenience shortcut for RpcSerializationFormatResolver.Default.tryGet(key). */
207
+ static tryGet(key: string): RpcSerializationFormat | undefined;
208
+ }
209
+ /**
210
+ * JSON text format — "json5np" (System.Text.Json, no polymorphism).
211
+ */
212
+ declare class RpcJsonSerializationFormat extends RpcSerializationFormat {
213
+ readonly key: string;
214
+ readonly isBinary = false;
215
+ readonly isCompact = false;
216
+ constructor(key: string);
217
+ serializeMessage(message: RpcMessage, args?: unknown[]): string;
218
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
219
+ splitBinaryFrame(): RpcDeserializedMessage[];
220
+ serializeBinaryFrame(): Uint8Array;
221
+ }
222
+ /**
223
+ * MessagePack binary format — "msgpack6" / "mempack6" (V5 wire format).
224
+ */
225
+ declare class RpcMessagePackSerializationFormat extends RpcSerializationFormat {
226
+ readonly key: string;
227
+ readonly isBinary = true;
228
+ readonly isCompact = false;
229
+ constructor(key: string);
230
+ serializeMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder): Uint8Array;
231
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
232
+ splitBinaryFrame(frame: Uint8Array, decoder?: Decoder): RpcDeserializedMessage[];
233
+ serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
234
+ createEncoder(): Encoder;
235
+ createDecoder(): Decoder;
236
+ }
237
+ /**
238
+ * MessagePack compact binary format — "msgpack6c" / "mempack6c" (V5Compact).
239
+ * Uses 4-byte method hash instead of full method name.
240
+ * Registry is passed via parameters — format instance is immutable.
241
+ */
242
+ declare class RpcMessagePackCompactSerializationFormat extends RpcSerializationFormat {
243
+ readonly key: string;
244
+ readonly isBinary = true;
245
+ readonly isCompact = true;
246
+ constructor(key: string);
247
+ serializeMessage(message: RpcMessage, args?: unknown[], encoder?: Encoder, registry?: RpcMethodRegistry): Uint8Array;
248
+ deserializeTextMessage(raw: string): RpcDeserializedMessage;
249
+ splitBinaryFrame(frame: Uint8Array, decoder?: Decoder, registry?: RpcMethodRegistry): RpcDeserializedMessage[];
250
+ serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;
251
+ createEncoder(): Encoder;
252
+ createDecoder(): Decoder;
253
+ }
254
+ /**
255
+ * Resolves RpcSerializationFormat instances by key, with a configurable default.
256
+ * Mirrors .NET RpcSerializationFormatResolver.
257
+ */
258
+ declare class RpcSerializationFormatResolver {
259
+ static DefaultFormats: readonly RpcSerializationFormat[];
260
+ static Default: RpcSerializationFormatResolver;
261
+ readonly defaultFormatKey: string;
262
+ readonly formats: ReadonlyMap<string, RpcSerializationFormat>;
263
+ get defaultFormat(): RpcSerializationFormat;
264
+ constructor(defaultFormatKey: string, formats?: Iterable<RpcSerializationFormat>);
265
+ get(key: string): RpcSerializationFormat;
266
+ tryGet(key: string): RpcSerializationFormat | undefined;
267
+ }
44
268
 
45
269
  /** Abstract WebSocket interface — works with both browser WebSocket and Node.js ws. */
46
270
  interface WebSocketLike {
47
271
  readonly readyState: number;
48
- send(data: string): void;
272
+ binaryType?: string;
273
+ send(data: string | ArrayBufferLike | Uint8Array | ArrayBufferView): void;
49
274
  close(code?: number, reason?: string): void;
50
275
  onopen: ((ev: unknown) => void) | null;
51
276
  onmessage: ((ev: {
@@ -63,35 +288,72 @@ declare const WebSocketState: {
63
288
  readonly CLOSING: 2;
64
289
  readonly CLOSED: 3;
65
290
  };
291
+ /** Received message — either text (string) or binary (already parsed). */
292
+ type RpcReceivedMessage = {
293
+ kind: 'text';
294
+ raw: string;
295
+ } | {
296
+ kind: 'binary';
297
+ message: RpcMessage;
298
+ args: unknown[];
299
+ };
66
300
  /** Abstract RPC connection — transport-agnostic interface for sending/receiving messages. */
67
301
  interface RpcConnection {
68
302
  readonly isOpen: boolean;
303
+ readonly binaryMode: boolean;
69
304
  readonly whenConnected: Promise<void>;
70
- readonly messageReceived: EventHandlerSet<string>;
305
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
71
306
  readonly closed: EventHandlerSet<{
72
307
  code: number;
73
308
  reason: string;
74
309
  }>;
310
+ /** Optional reusable msgpack encoder for outbound binary messages.
311
+ * Callers passing this to `serializeBinaryMessage` skip the per-call
312
+ * Encoder construction + resizeBuffer growth. Implementations that
313
+ * don't support binary serialization can leave it undefined. */
314
+ readonly encoder?: Encoder;
75
315
  send(serializedMessage: string): void;
316
+ sendBinary(data: Uint8Array): void;
76
317
  close(code?: number, reason?: string): void;
77
318
  }
78
- /** WebSocket-based RpcConnection — handles frame splitting and message queueing. */
319
+ /** WebSocket-based RpcConnection — handles frame splitting, binary/text modes, and message queueing. */
79
320
  declare class RpcWebSocketConnection implements RpcConnection {
80
321
  private _ws;
81
322
  private _sendBuffer;
82
323
  private _connected;
83
- readonly messageReceived: EventHandlerSet<string>;
324
+ /** Per-connection msgpack encoder — reused across outbound messages to
325
+ * amortise construction cost and to keep the internal write buffer at
326
+ * its largest observed size (avoids repeated `resizeBuffer` growth on
327
+ * large video frames). See serializeBinaryMessage() for details. */
328
+ private _encoder;
329
+ /** Per-connection msgpack decoder — reused across inbound messages
330
+ * rather than reconstructing one for every `decodeMulti` call. */
331
+ private _decoder;
332
+ /** Exposed for callers who need to encode outbound binary messages
333
+ * through this connection (e.g. `serializeBinaryMessage`). Safe to
334
+ * pass to synchronous encode calls on the same event-loop turn. */
335
+ get encoder(): Encoder;
336
+ get decoder(): Decoder;
337
+ private _format;
338
+ private _methodRegistry;
339
+ readonly binaryMode: boolean;
340
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
84
341
  readonly closed: EventHandlerSet<{
85
342
  code: number;
86
343
  reason: string;
87
344
  }>;
88
345
  readonly error: EventHandlerSet<unknown>;
89
- constructor(ws: WebSocketLike);
346
+ constructor(ws: WebSocketLike, binaryMode?: boolean, format?: RpcSerializationFormat, methodRegistry?: RpcMethodRegistry);
90
347
  get isOpen(): boolean;
91
348
  get whenConnected(): Promise<void>;
92
349
  send(serializedMessage: string): void;
93
- sendBatch(messages: string[]): void;
350
+ sendBinary(data: Uint8Array): void;
351
+ sendTextBatch(messages: string[]): void;
352
+ sendBinaryBatch(messages: Uint8Array[]): void;
353
+ /** Split a binary frame using the format (compact-aware) or default splitter. */
354
+ private _splitBinary;
94
355
  close(code?: number, reason?: string): void;
356
+ private _sendRaw;
95
357
  private _flush;
96
358
  }
97
359
 
@@ -99,7 +361,8 @@ declare class RpcWebSocketConnection implements RpcConnection {
99
361
  declare class RpcMessageChannelConnection implements RpcConnection {
100
362
  private _port;
101
363
  private _open;
102
- readonly messageReceived: EventHandlerSet<string>;
364
+ readonly binaryMode = false;
365
+ readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;
103
366
  readonly closed: EventHandlerSet<{
104
367
  code: number;
105
368
  reason: string;
@@ -108,10 +371,14 @@ declare class RpcMessageChannelConnection implements RpcConnection {
108
371
  constructor(port: MessagePort);
109
372
  get isOpen(): boolean;
110
373
  send(serializedMessage: string): void;
374
+ sendBinary(data: Uint8Array): void;
111
375
  close(code?: number, reason?: string): void;
112
376
  }
113
377
  /** Creates a pair of connected RpcMessageChannelConnections for testing. */
114
- declare function createMessageChannelPair(): [RpcMessageChannelConnection, RpcMessageChannelConnection];
378
+ declare function createMessageChannelPair(): [
379
+ RpcMessageChannelConnection,
380
+ RpcMessageChannelConnection
381
+ ];
115
382
 
116
383
  /** Describes a single method on an RPC service. */
117
384
  interface RpcMethodDef {
@@ -153,8 +420,8 @@ declare class RpcOutboundCall {
153
420
  /** Whether to remove this call from the tracker on $sys.Ok. Default: true.
154
421
  * Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */
155
422
  readonly removeOnOk: boolean;
156
- /** The serialized wire message — stored for re-sending on reconnect. */
157
- serializedMessage: string;
423
+ /** The serialized wire data — stored for re-sending on reconnect. */
424
+ serializedWireData: string | Uint8Array;
158
425
  constructor(callId: number, method: string);
159
426
  /** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */
160
427
  onDisconnect(): void;
@@ -213,20 +480,49 @@ declare class RpcServiceHost {
213
480
  getMethodDef(wireMethod: string): RpcMethodDef | undefined;
214
481
  }
215
482
 
216
- /** Sends system RPC messages — like .NET's RpcSystemCallSender. */
483
+ /**
484
+ * Sends system RPC messages — like .NET's RpcSystemCallSender.
485
+ *
486
+ * Hot-path allocation notes: `item`, `batch`, `ack`, and `end` are called
487
+ * once per stream frame (or once per ack window). Naively building a fresh
488
+ * `{ Method, RelatedId }` envelope object plus a fresh two-element arg array
489
+ * on every call showed up as ~2-4% self time + non-trivial GC pressure in
490
+ * the 300-pull video-load-test profile.
491
+ *
492
+ * The sender itself is a per-Hub singleton and `_send`/format.serializeMessage
493
+ * are fully synchronous (no awaits, no re-entrancy on a single-threaded event
494
+ * loop), so we reuse a small pool of preallocated envelope + arg-array slots
495
+ * per hot method. Mutate fields just before the send call; the encoder reads
496
+ * them synchronously and produces a detached copy, after which the slot is
497
+ * free to be mutated again on the next call.
498
+ */
217
499
  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?: {
500
+ private readonly _itemEnv;
501
+ private readonly _batchEnv;
502
+ private readonly _endEnv;
503
+ private readonly _ackEnv;
504
+ private readonly _ackEndEnv;
505
+ private readonly _itemArgs;
506
+ private readonly _batchArgs;
507
+ private readonly _endArgs;
508
+ private readonly _ackArgs;
509
+ private readonly _ackEndArgs;
510
+ private static readonly NO_ERROR;
511
+ registry?: RpcMethodRegistry;
512
+ private _send;
513
+ handshake(conn: RpcConnection, format: RpcSerializationFormat, peerId: string, hubId: string, index: number): void;
514
+ ok(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number, result: unknown): void;
515
+ error(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number, error: unknown): void;
516
+ cancel(conn: RpcConnection, format: RpcSerializationFormat, relatedId: number): void;
517
+ keepAlive(conn: RpcConnection, format: RpcSerializationFormat, activeCallIds: number[]): void;
518
+ item(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, item: unknown): void;
519
+ batch(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, items: unknown[]): void;
520
+ end(conn: RpcConnection, format: RpcSerializationFormat, localId: number, index: number, error?: {
521
+ TypeRef: string;
226
522
  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;
523
+ }): void;
524
+ ack(conn: RpcConnection, format: RpcSerializationFormat, localId: number, nextIndex: number, hostId: string): void;
525
+ ackEnd(conn: RpcConnection, format: RpcSerializationFormat, localId: number, hostId: string): void;
230
526
  }
231
527
 
232
528
  /** Central RPC coordinator — manages peers, services, and configuration. */
@@ -236,6 +532,9 @@ declare class RpcHub {
236
532
  readonly serviceHost: RpcServiceHost;
237
533
  readonly systemCallSender: RpcSystemCallSender;
238
534
  systemCallHandler: RpcSystemCallHandler;
535
+ /** Method registry for compact format hash ↔ name resolution.
536
+ * Created lazily, populated by addService/addClient. */
537
+ readonly registry: RpcMethodRegistry;
239
538
  constructor(hubId?: string);
240
539
  addPeer(peer: RpcPeer): void;
241
540
  removePeer(ref: string): void;
@@ -281,6 +580,7 @@ declare class RpcRemoteObjectTracker {
281
580
  private _objects;
282
581
  register(obj: IRpcObject): void;
283
582
  get(localId: number): IRpcObject | undefined;
583
+ keys(): IterableIterator<number>;
284
584
  unregister(obj: IRpcObject): void;
285
585
  disconnectAll(): void;
286
586
  reconnectAll(): void;
@@ -292,16 +592,11 @@ declare class RpcSharedObjectTracker {
292
592
  nextId(): number;
293
593
  register(obj: IRpcObject): void;
294
594
  get(localId: number): IRpcObject | undefined;
595
+ keys(): IterableIterator<number>;
295
596
  unregister(obj: IRpcObject): void;
296
597
  disconnectAll(): void;
297
598
  }
298
599
 
299
- /**
300
- * Default serialization format for RpcClientPeer connections.
301
- * TS uses plain JSON.stringify without polymorphic type wrapping,
302
- * so "json5np" (System.Text.Json, no polymorphism) is the correct match.
303
- */
304
- declare const DEFAULT_SERIALIZATION_FORMAT = "json5np";
305
600
  /** WebSocket close code sent by the server when the client's serialization format is unsupported. */
306
601
  declare const RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
307
602
  /** Builds the WebSocket connection URL for an RpcClientPeer. */
@@ -348,11 +643,15 @@ declare abstract class RpcPeer {
348
643
  get connection(): RpcConnection | undefined;
349
644
  get isConnected(): boolean;
350
645
  protected setupConnection(conn: RpcConnection): void;
646
+ /** The serialization format used by this peer. Override in subclasses. */
647
+ get format(): RpcSerializationFormat;
351
648
  call(method: string, args?: unknown[], options?: RpcCallOptions): RpcOutboundCall;
352
649
  callNoWait(method: string, args?: unknown[]): void;
353
650
  close(): void;
354
651
  /** Send any messages buffered while disconnected. Call after connection + handshake are ready. */
355
652
  protected _flushPendingSends(): void;
653
+ /** Send pre-serialized wire data through the current connection. */
654
+ protected _sendWireData(data: string | Uint8Array): void;
356
655
  /** Override in subclasses to handle the remote peer's handshake response. */
357
656
  protected _onHandshakeReceived(_handshake: RemoteHandshake): void;
358
657
  private _handleMessage;
@@ -394,7 +693,9 @@ declare class RpcClientPeer extends RpcPeer {
394
693
  readonly reconnectsAtChanged: EventHandlerSet<void>;
395
694
  get reconnectsAt(): number;
396
695
  private _setReconnectsAt;
696
+ private _format;
397
697
  constructor(hub: RpcHub, url: string, serializationFormat?: string);
698
+ get format(): RpcSerializationFormat;
398
699
  get connectionKind(): RpcPeerConnectionKind;
399
700
  /** One-shot connection for tests — no reconnection loop, no handshake. */
400
701
  connectWith(conn: RpcConnection): void;
@@ -431,6 +732,7 @@ interface RpcStreamRef {
431
732
  readonly ackPeriod: number;
432
733
  readonly ackAdvance: number;
433
734
  readonly allowReconnect: boolean;
735
+ readonly isRealTime: boolean;
434
736
  }
435
737
  /**
436
738
  * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{ackAdvance}".
@@ -477,6 +779,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
477
779
  dispose(): void;
478
780
  private _notifyConsumer;
479
781
  private _maybeSendAck;
782
+ private static readonly _emptyGuid;
480
783
  private _sendAck;
481
784
  private _sendAckEnd;
482
785
  }
@@ -504,17 +807,21 @@ declare class RpcStreamSender<T> implements IRpcObject {
504
807
  readonly id: RpcObjectId;
505
808
  readonly kind: 0;
506
809
  readonly allowReconnect: boolean;
810
+ readonly isRealTime: boolean;
811
+ readonly canSkipTo: (item: T) => boolean;
507
812
  readonly peer: RpcPeer;
508
813
  readonly ackPeriod: number;
509
814
  readonly ackAdvance: number;
510
815
  private _nextIndex;
511
816
  private _ended;
512
817
  private _started;
513
- constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean);
818
+ private _lastAckedIndex;
819
+ private _ackWaiting;
820
+ constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean);
514
821
  /** Returns the stream reference string for the $sys.Ok response. */
515
822
  toRef(): string;
516
823
  /** Called by system call handler when $sys.Ack is received from the client. */
517
- onAck(_nextIndex: number, _hostId: string): void;
824
+ onAck(nextIndex: number, _hostId: string): void;
518
825
  /** Called by system call handler when $sys.AckEnd is received from the client. */
519
826
  onAckEnd(_hostId: string): void;
520
827
  /** Send a single item to the client. */
@@ -526,8 +833,15 @@ declare class RpcStreamSender<T> implements IRpcObject {
526
833
  /**
527
834
  * Consume an AsyncIterable and send all items to the client.
528
835
  * Waits for the client's initial Ack before starting to pump items.
836
+ *
837
+ * When `isRealTime` is true, applies flow control based on `ackAdvance`:
838
+ * if the sender is `ackAdvance` items ahead of the last ACK, it enters
839
+ * skip mode — draining the source and discarding items until it finds
840
+ * one where `canSkipTo` returns true.
529
841
  */
530
842
  writeFrom(source: AsyncIterable<T>): Promise<void>;
843
+ /** Wait until _nextIndex < _lastAckedIndex + ackAdvance. */
844
+ private _waitForAckBudget;
531
845
  reconnect(): void;
532
846
  disconnect(): void;
533
847
  }
@@ -596,4 +910,24 @@ declare class RpcPeerStateMonitor {
596
910
  private _scheduleRecompute;
597
911
  }
598
912
 
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 };
913
+ /**
914
+ * Compute XXH3-64 hash of a byte array. Returns a BigInt (unsigned 64-bit).
915
+ * Pure TypeScript, synchronous, supports any input length.
916
+ */
917
+ declare function xxh3_64(input: Uint8Array, seed?: bigint): bigint;
918
+ /**
919
+ * Compute XXH3-64 hash of a UTF-8 string. Returns a BigInt (unsigned 64-bit).
920
+ */
921
+ declare function xxh3_64str(input: string, seed?: bigint): bigint;
922
+ /**
923
+ * Compute the RPC method hash matching .NET's RpcMethodRef.ComputeHashCode.
924
+ * Returns a 32-bit signed integer (lower 32 bits of XXH3-64 of [prefix + utf8Name]).
925
+ *
926
+ * The prefix is uint32 LE of (67211 * utf8Name.length), which ensures methods
927
+ * with different name lengths but similar text produce different hashes.
928
+ *
929
+ * Computed on demand — caller should cache the result per method name.
930
+ */
931
+ declare function computeMethodHash(methodName: string): number;
932
+
933
+ export { ARG_DELIMITER, 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, RpcSerializationFormatResolver, 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 };