murow 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,8 @@ import type { IntentRegistry } from "../protocol/intent/intent-registry";
2
2
  import type { SnapshotRegistry } from "../protocol/snapshot/snapshot-registry";
3
3
  import type { Snapshot } from "../protocol/snapshot/snapshot";
4
4
  import type { Intent } from "../protocol/intent/intent";
5
+ import type { RpcRegistry } from "../protocol/rpc/rpc-registry";
6
+ import type { DefinedRpc } from "../protocol/rpc/rpc";
5
7
  import { type TransportAdapter, type NetworkConfig } from "./types";
6
8
  /**
7
9
  * Configuration for ClientNetwork
@@ -13,6 +15,8 @@ export interface ClientNetworkConfig<TSnapshots> {
13
15
  intentRegistry: IntentRegistry;
14
16
  /** Snapshot registry for decoding server snapshots */
15
17
  snapshotRegistry: SnapshotRegistry<TSnapshots>;
18
+ /** RPC registry for bidirectional remote procedure calls (optional) */
19
+ rpcRegistry?: RpcRegistry;
16
20
  /** Network configuration */
17
21
  config?: NetworkConfig;
18
22
  }
@@ -42,9 +46,12 @@ export declare class ClientNetwork<TSnapshots = unknown> {
42
46
  private transport;
43
47
  private intentRegistry;
44
48
  private snapshotRegistry;
49
+ private rpcRegistry?;
45
50
  private config;
46
51
  /** Snapshot type handlers: type -> handler[] (supports multiple handlers) */
47
52
  private snapshotHandlers;
53
+ /** RPC method handlers: method -> handler[] (supports multiple handlers) */
54
+ private rpcHandlers;
48
55
  /** Connection lifecycle handlers */
49
56
  private connectHandlers;
50
57
  private disconnectHandlers;
@@ -98,6 +105,46 @@ export declare class ClientNetwork<TSnapshots = unknown> {
98
105
  * @returns Unsubscribe function to remove this handler
99
106
  */
100
107
  onSnapshot<T extends Partial<TSnapshots>>(type: string, handler: (snapshot: Snapshot<T>) => void): () => void;
108
+ /**
109
+ * Send an RPC to the server (type-safe)
110
+ *
111
+ * @template TSchema The RPC data type
112
+ * @param rpc The RPC definition created by defineRpc()
113
+ * @param data The RPC data to send
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const BuyItem = defineRpc({
118
+ * method: 'buyItem',
119
+ * schema: { itemId: BinaryCodec.string(32) }
120
+ * });
121
+ *
122
+ * client.sendRpc(BuyItem, { itemId: 'long_sword' });
123
+ * ```
124
+ */
125
+ sendRpc<TSchema extends Record<string, any>>(rpc: DefinedRpc<TSchema>, data: TSchema): void;
126
+ /**
127
+ * Register a handler for incoming RPCs from the server (type-safe)
128
+ * Supports multiple handlers per RPC method
129
+ *
130
+ * @template TSchema The RPC data type
131
+ * @param rpc The RPC definition created by defineRpc()
132
+ * @param handler Callback function to handle the RPC
133
+ * @returns Unsubscribe function to remove this handler
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const MatchCountdown = defineRpc({
138
+ * method: 'matchCountdown',
139
+ * schema: { secondsRemaining: BinaryCodec.u8 }
140
+ * });
141
+ *
142
+ * client.onRpc(MatchCountdown, (rpc) => {
143
+ * console.log(`Match starting in ${rpc.secondsRemaining}s`);
144
+ * });
145
+ * ```
146
+ */
147
+ onRpc<TSchema extends Record<string, any>>(rpc: DefinedRpc<TSchema>, handler: (data: TSchema) => void): () => void;
101
148
  /**
102
149
  * Register a handler for connection events
103
150
  */
@@ -138,6 +185,10 @@ export declare class ClientNetwork<TSnapshots = unknown> {
138
185
  * Decode and handle a snapshot from server
139
186
  */
140
187
  private handleSnapshot;
188
+ /**
189
+ * Handle incoming RPC message from server
190
+ */
191
+ private handleRpc;
141
192
  /**
142
193
  * Handle disconnection from server
143
194
  */
@@ -25,6 +25,8 @@ export class ClientNetwork {
25
25
  constructor(config) {
26
26
  /** Snapshot type handlers: type -> handler[] (supports multiple handlers) */
27
27
  this.snapshotHandlers = new Map();
28
+ /** RPC method handlers: method -> handler[] (supports multiple handlers) */
29
+ this.rpcHandlers = new Map();
28
30
  /** Connection lifecycle handlers */
29
31
  this.connectHandlers = [];
30
32
  this.disconnectHandlers = [];
@@ -43,6 +45,7 @@ export class ClientNetwork {
43
45
  this.transport = config.transport;
44
46
  this.intentRegistry = config.intentRegistry;
45
47
  this.snapshotRegistry = config.snapshotRegistry;
48
+ this.rpcRegistry = config.rpcRegistry;
46
49
  this.config = {
47
50
  maxMessageSize: config.config?.maxMessageSize ?? 65536,
48
51
  debug: config.config?.debug ?? false,
@@ -149,6 +152,93 @@ export class ClientNetwork {
149
152
  }
150
153
  };
151
154
  }
155
+ /**
156
+ * Send an RPC to the server (type-safe)
157
+ *
158
+ * @template TSchema The RPC data type
159
+ * @param rpc The RPC definition created by defineRpc()
160
+ * @param data The RPC data to send
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const BuyItem = defineRpc({
165
+ * method: 'buyItem',
166
+ * schema: { itemId: BinaryCodec.string(32) }
167
+ * });
168
+ *
169
+ * client.sendRpc(BuyItem, { itemId: 'long_sword' });
170
+ * ```
171
+ */
172
+ sendRpc(rpc, data) {
173
+ if (!this.rpcRegistry) {
174
+ throw new Error('RpcRegistry not configured. Pass rpcRegistry to ClientNetworkConfig.');
175
+ }
176
+ if (!this.connected) {
177
+ this.log("Cannot send RPC: not connected");
178
+ return;
179
+ }
180
+ // Client-side rate limiting
181
+ if (!this.checkRateLimit()) {
182
+ this.log("Rate limit exceeded, dropping RPC");
183
+ return;
184
+ }
185
+ try {
186
+ // Encode RPC
187
+ const rpcData = this.rpcRegistry.encode(rpc, data);
188
+ // Wrap with message type header
189
+ const message = new Uint8Array(1 + rpcData.byteLength);
190
+ message[0] = MessageType.CUSTOM;
191
+ message.set(rpcData, 1);
192
+ // Send to server
193
+ this.transport.send(message);
194
+ this.log(`Sent RPC (method: ${rpc.method})`);
195
+ }
196
+ catch (error) {
197
+ this.log(`Failed to send RPC: ${error}`);
198
+ }
199
+ }
200
+ /**
201
+ * Register a handler for incoming RPCs from the server (type-safe)
202
+ * Supports multiple handlers per RPC method
203
+ *
204
+ * @template TSchema The RPC data type
205
+ * @param rpc The RPC definition created by defineRpc()
206
+ * @param handler Callback function to handle the RPC
207
+ * @returns Unsubscribe function to remove this handler
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * const MatchCountdown = defineRpc({
212
+ * method: 'matchCountdown',
213
+ * schema: { secondsRemaining: BinaryCodec.u8 }
214
+ * });
215
+ *
216
+ * client.onRpc(MatchCountdown, (rpc) => {
217
+ * console.log(`Match starting in ${rpc.secondsRemaining}s`);
218
+ * });
219
+ * ```
220
+ */
221
+ onRpc(rpc, handler) {
222
+ if (!this.rpcRegistry) {
223
+ throw new Error('RpcRegistry not configured. Pass rpcRegistry to ClientNetworkConfig.');
224
+ }
225
+ let handlers = this.rpcHandlers.get(rpc.method);
226
+ if (!handlers) {
227
+ handlers = [];
228
+ this.rpcHandlers.set(rpc.method, handlers);
229
+ }
230
+ handlers.push(handler);
231
+ // Return unsubscribe function
232
+ return () => {
233
+ const handlers = this.rpcHandlers.get(rpc.method);
234
+ if (handlers) {
235
+ const index = handlers.indexOf(handler);
236
+ if (index > -1) {
237
+ handlers.splice(index, 1);
238
+ }
239
+ }
240
+ };
241
+ }
152
242
  /**
153
243
  * Register a handler for connection events
154
244
  */
@@ -278,8 +368,7 @@ export class ClientNetwork {
278
368
  this.log("Received heartbeat from server");
279
369
  break;
280
370
  case MessageType.CUSTOM:
281
- // Could add custom message handlers here
282
- this.log("Received custom message from server");
371
+ this.handleRpc(payload);
283
372
  break;
284
373
  default:
285
374
  this.log(`Unknown message type: ${messageType}`);
@@ -313,6 +402,38 @@ export class ClientNetwork {
313
402
  this.log(`Failed to decode snapshot: ${error}`);
314
403
  }
315
404
  }
405
+ /**
406
+ * Handle incoming RPC message from server
407
+ */
408
+ handleRpc(data) {
409
+ if (!this.rpcRegistry) {
410
+ this.log("Received RPC but RpcRegistry not configured");
411
+ return;
412
+ }
413
+ try {
414
+ // Decode using RPC registry (returns { method, data })
415
+ const decoded = this.rpcRegistry.decode(data);
416
+ this.log(`Received RPC (method: ${decoded.method})`);
417
+ // Call all method-specific handlers if registered
418
+ const handlers = this.rpcHandlers.get(decoded.method);
419
+ if (handlers && handlers.length > 0) {
420
+ for (const handler of handlers) {
421
+ try {
422
+ handler(decoded.data);
423
+ }
424
+ catch (error) {
425
+ this.log(`Error in RPC handler: ${error}`);
426
+ }
427
+ }
428
+ }
429
+ else {
430
+ this.log(`No handler registered for RPC method: ${decoded.method}`);
431
+ }
432
+ }
433
+ catch (error) {
434
+ this.log(`Failed to decode RPC: ${error}`);
435
+ }
436
+ }
316
437
  /**
317
438
  * Handle disconnection from server
318
439
  */
@@ -2,6 +2,8 @@ import type { IntentRegistry } from "../protocol/intent/intent-registry";
2
2
  import type { SnapshotRegistry } from "../protocol/snapshot/snapshot-registry";
3
3
  import type { Snapshot } from "../protocol/snapshot/snapshot";
4
4
  import type { Intent } from "../protocol/intent/intent";
5
+ import type { RpcRegistry } from "../protocol/rpc/rpc-registry";
6
+ import type { DefinedRpc } from "../protocol/rpc/rpc";
5
7
  import { MessagePriority, type PeerState, type ServerTransportAdapter, type TransportAdapter, type NetworkConfig } from "./types";
6
8
  import { DefinedIntent } from "../protocol";
7
9
  /**
@@ -14,6 +16,8 @@ export interface ServerNetworkConfig<TPeer extends TransportAdapter, TSnapshots>
14
16
  intentRegistry: IntentRegistry;
15
17
  /** Factory to create per-peer snapshot registries */
16
18
  createPeerSnapshotRegistry: () => SnapshotRegistry<TSnapshots>;
19
+ /** RPC registry for bidirectional remote procedure calls (optional) */
20
+ rpcRegistry?: RpcRegistry;
17
21
  /** Network configuration */
18
22
  config?: NetworkConfig;
19
23
  }
@@ -70,6 +74,7 @@ export declare class ServerNetwork<TPeer extends TransportAdapter = TransportAda
70
74
  private transport;
71
75
  private intentRegistry;
72
76
  private createPeerSnapshotRegistry;
77
+ private rpcRegistry?;
73
78
  private config;
74
79
  /** Per-peer state tracking */
75
80
  private peers;
@@ -83,6 +88,8 @@ export declare class ServerNetwork<TPeer extends TransportAdapter = TransportAda
83
88
  private intentHandlers;
84
89
  /** Global intent handler called for ALL intents before specific handlers */
85
90
  private anyIntentHandlers;
91
+ /** RPC method handlers: method -> handler[] (supports multiple handlers) */
92
+ private rpcHandlers;
86
93
  /** Connection lifecycle handlers */
87
94
  private connectionHandlers;
88
95
  private disconnectionHandlers;
@@ -133,6 +140,62 @@ export declare class ServerNetwork<TPeer extends TransportAdapter = TransportAda
133
140
  * Register a handler for disconnections
134
141
  */
135
142
  onDisconnection(handler: (peerId: string) => void): void;
143
+ /**
144
+ * Send an RPC to a specific peer (type-safe)
145
+ *
146
+ * @template TSchema The RPC data type
147
+ * @param peerId The peer to send to
148
+ * @param rpc The RPC definition created by defineRpc()
149
+ * @param data The RPC data to send
150
+ * @param priority Message priority (default: NORMAL)
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * const MatchCountdown = defineRpc({
155
+ * method: 'matchCountdown',
156
+ * schema: { secondsRemaining: BinaryCodec.u8 }
157
+ * });
158
+ *
159
+ * server.sendRpc(peerId, MatchCountdown, { secondsRemaining: 10 });
160
+ * ```
161
+ */
162
+ sendRpc<TSchema extends Record<string, any>>(peerId: string, rpc: DefinedRpc<TSchema>, data: TSchema, priority?: MessagePriority): void;
163
+ /**
164
+ * Send an RPC to all connected peers (broadcast)
165
+ *
166
+ * @template TSchema The RPC data type
167
+ * @param rpc The RPC definition created by defineRpc()
168
+ * @param data The RPC data to send
169
+ * @param priority Message priority (default: NORMAL)
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * server.sendRpcBroadcast(MatchCountdown, { secondsRemaining: 3 });
174
+ * ```
175
+ */
176
+ sendRpcBroadcast<TSchema extends Record<string, any>>(rpc: DefinedRpc<TSchema>, data: TSchema, priority?: MessagePriority): void;
177
+ /**
178
+ * Register a handler for incoming RPCs from clients (type-safe)
179
+ * Supports multiple handlers per RPC method
180
+ *
181
+ * @template TSchema The RPC data type
182
+ * @param rpc The RPC definition created by defineRpc()
183
+ * @param handler Callback function to handle the RPC
184
+ * @returns Unsubscribe function to remove this handler
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * const BuyItem = defineRpc({
189
+ * method: 'buyItem',
190
+ * schema: { itemId: BinaryCodec.string(32) }
191
+ * });
192
+ *
193
+ * server.onRpc(BuyItem, (peerId, rpc) => {
194
+ * console.log(`${peerId} wants to buy ${rpc.itemId}`);
195
+ * });
196
+ * ```
197
+ */
198
+ onRpc<TSchema extends Record<string, any>>(rpc: DefinedRpc<TSchema>, handler: (peerId: string, data: TSchema) => void): () => void;
136
199
  /**
137
200
  * Send a snapshot to a specific peer using their dedicated snapshot registry (type-safe)
138
201
  * @template T The specific snapshot update type
@@ -297,6 +360,10 @@ export declare class ServerNetwork<TPeer extends TransportAdapter = TransportAda
297
360
  * Decode and handle an intent from a peer
298
361
  */
299
362
  private handleIntent;
363
+ /**
364
+ * Handle incoming RPC message from a peer
365
+ */
366
+ private handleRpc;
300
367
  /**
301
368
  * Check rate limit for a peer
302
369
  * Returns true if message should be processed, false if rate limit exceeded
@@ -63,6 +63,8 @@ export class ServerNetwork {
63
63
  this.intentHandlers = new Map();
64
64
  /** Global intent handler called for ALL intents before specific handlers */
65
65
  this.anyIntentHandlers = [];
66
+ /** RPC method handlers: method -> handler[] (supports multiple handlers) */
67
+ this.rpcHandlers = new Map();
66
68
  /** Connection lifecycle handlers */
67
69
  this.connectionHandlers = [];
68
70
  this.disconnectionHandlers = [];
@@ -73,6 +75,7 @@ export class ServerNetwork {
73
75
  this.transport = config.transport;
74
76
  this.intentRegistry = config.intentRegistry;
75
77
  this.createPeerSnapshotRegistry = config.createPeerSnapshotRegistry;
78
+ this.rpcRegistry = config.rpcRegistry;
76
79
  this.config = {
77
80
  maxMessageSize: config.config?.maxMessageSize ?? 65536,
78
81
  debug: config.config?.debug ?? false,
@@ -173,6 +176,129 @@ export class ServerNetwork {
173
176
  onDisconnection(handler) {
174
177
  this.disconnectionHandlers.push(handler);
175
178
  }
179
+ /**
180
+ * Send an RPC to a specific peer (type-safe)
181
+ *
182
+ * @template TSchema The RPC data type
183
+ * @param peerId The peer to send to
184
+ * @param rpc The RPC definition created by defineRpc()
185
+ * @param data The RPC data to send
186
+ * @param priority Message priority (default: NORMAL)
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const MatchCountdown = defineRpc({
191
+ * method: 'matchCountdown',
192
+ * schema: { secondsRemaining: BinaryCodec.u8 }
193
+ * });
194
+ *
195
+ * server.sendRpc(peerId, MatchCountdown, { secondsRemaining: 10 });
196
+ * ```
197
+ */
198
+ sendRpc(peerId, rpc, data, priority = MessagePriority.NORMAL) {
199
+ if (!this.rpcRegistry) {
200
+ throw new Error('RpcRegistry not configured. Pass rpcRegistry to ServerNetworkConfig.');
201
+ }
202
+ const peer = this.peers.get(peerId);
203
+ if (!peer) {
204
+ this.log(`Cannot send RPC to unknown peer: ${peerId}`);
205
+ return;
206
+ }
207
+ try {
208
+ // Encode RPC
209
+ const rpcData = this.rpcRegistry.encode(rpc, data);
210
+ // Wrap with message type header (use pool if enabled)
211
+ let message;
212
+ if (this.messagePool) {
213
+ message = this.messagePool.wrap(MessageType.CUSTOM, rpcData);
214
+ }
215
+ else {
216
+ message = new Uint8Array(1 + rpcData.byteLength);
217
+ message[0] = MessageType.CUSTOM;
218
+ message.set(rpcData, 1);
219
+ }
220
+ // Check backpressure and queue if necessary
221
+ if (peer.isBackpressured || peer.sendQueue.length > 0) {
222
+ // Peer is experiencing backpressure, queue the message with priority
223
+ this.queueMessage(peer, message, priority);
224
+ // Release pooled buffer since we copied it in queueMessage
225
+ if (this.messagePool) {
226
+ this.messagePool.release(message);
227
+ }
228
+ return;
229
+ }
230
+ // Try to send immediately
231
+ this.sendMessageToPeer(peer, message);
232
+ // Release pooled buffer after send
233
+ if (this.messagePool) {
234
+ this.messagePool.release(message);
235
+ }
236
+ this.log(`Sent RPC (method: ${rpc.method}) to peer: ${peerId}`);
237
+ }
238
+ catch (error) {
239
+ this.log(`Failed to send RPC to peer ${peerId}: ${error}`);
240
+ }
241
+ }
242
+ /**
243
+ * Send an RPC to all connected peers (broadcast)
244
+ *
245
+ * @template TSchema The RPC data type
246
+ * @param rpc The RPC definition created by defineRpc()
247
+ * @param data The RPC data to send
248
+ * @param priority Message priority (default: NORMAL)
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * server.sendRpcBroadcast(MatchCountdown, { secondsRemaining: 3 });
253
+ * ```
254
+ */
255
+ sendRpcBroadcast(rpc, data, priority = MessagePriority.NORMAL) {
256
+ for (const peerId of this.getPeerIds()) {
257
+ this.sendRpc(peerId, rpc, data, priority);
258
+ }
259
+ }
260
+ /**
261
+ * Register a handler for incoming RPCs from clients (type-safe)
262
+ * Supports multiple handlers per RPC method
263
+ *
264
+ * @template TSchema The RPC data type
265
+ * @param rpc The RPC definition created by defineRpc()
266
+ * @param handler Callback function to handle the RPC
267
+ * @returns Unsubscribe function to remove this handler
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * const BuyItem = defineRpc({
272
+ * method: 'buyItem',
273
+ * schema: { itemId: BinaryCodec.string(32) }
274
+ * });
275
+ *
276
+ * server.onRpc(BuyItem, (peerId, rpc) => {
277
+ * console.log(`${peerId} wants to buy ${rpc.itemId}`);
278
+ * });
279
+ * ```
280
+ */
281
+ onRpc(rpc, handler) {
282
+ if (!this.rpcRegistry) {
283
+ throw new Error('RpcRegistry not configured. Pass rpcRegistry to ServerNetworkConfig.');
284
+ }
285
+ let handlers = this.rpcHandlers.get(rpc.method);
286
+ if (!handlers) {
287
+ handlers = [];
288
+ this.rpcHandlers.set(rpc.method, handlers);
289
+ }
290
+ handlers.push(handler);
291
+ // Return unsubscribe function
292
+ return () => {
293
+ const handlers = this.rpcHandlers.get(rpc.method);
294
+ if (handlers) {
295
+ const index = handlers.indexOf(handler);
296
+ if (index > -1) {
297
+ handlers.splice(index, 1);
298
+ }
299
+ }
300
+ };
301
+ }
176
302
  /**
177
303
  * Send a snapshot to a specific peer using their dedicated snapshot registry (type-safe)
178
304
  * @template T The specific snapshot update type
@@ -685,8 +811,7 @@ export class ServerNetwork {
685
811
  this.log(`Received heartbeat from peer ${peerId}`);
686
812
  break;
687
813
  case MessageType.CUSTOM:
688
- // Could add custom message handlers here
689
- this.log(`Received custom message from peer ${peerId}`);
814
+ this.handleRpc(peerId, payload);
690
815
  break;
691
816
  default:
692
817
  this.log(`Unknown message type ${messageType} from peer ${peerId}`);
@@ -737,6 +862,43 @@ export class ServerNetwork {
737
862
  this.log(`Failed to decode intent from peer ${peerId}: ${error}`);
738
863
  }
739
864
  }
865
+ /**
866
+ * Handle incoming RPC message from a peer
867
+ */
868
+ handleRpc(peerId, data) {
869
+ if (!this.rpcRegistry) {
870
+ this.log("Received RPC but RpcRegistry not configured");
871
+ return;
872
+ }
873
+ // Rate limiting check
874
+ if (!this.checkRateLimit(peerId)) {
875
+ this.log(`Rate limit exceeded for peer ${peerId}, dropping RPC`);
876
+ return;
877
+ }
878
+ try {
879
+ // Decode using RPC registry (returns { method, data })
880
+ const decoded = this.rpcRegistry.decode(data);
881
+ this.log(`Received RPC (method: ${decoded.method}) from peer ${peerId}`);
882
+ // Call all method-specific handlers if registered
883
+ const handlers = this.rpcHandlers.get(decoded.method);
884
+ if (handlers && handlers.length > 0) {
885
+ for (const handler of handlers) {
886
+ try {
887
+ handler(peerId, decoded.data);
888
+ }
889
+ catch (error) {
890
+ this.log(`Error in RPC handler: ${error}`);
891
+ }
892
+ }
893
+ }
894
+ else {
895
+ this.log(`No handler registered for RPC method: ${decoded.method}`);
896
+ }
897
+ }
898
+ catch (error) {
899
+ this.log(`Failed to decode RPC from peer ${peerId}: ${error}`);
900
+ }
901
+ }
740
902
  /**
741
903
  * Check rate limit for a peer
742
904
  * Returns true if message should be processed, false if rate limit exceeded
@@ -89,3 +89,4 @@
89
89
  */
90
90
  export * from "./intent";
91
91
  export * from "./snapshot";
92
+ export * from "./rpc";
@@ -89,3 +89,4 @@
89
89
  */
90
90
  export * from "./intent";
91
91
  export * from "./snapshot";
92
+ export * from "./rpc";
@@ -0,0 +1,72 @@
1
+ import type { DefinedRpc } from "./rpc";
2
+ /**
3
+ * Configuration for defining an RPC type.
4
+ * @template S The schema type describing the RPC's data fields
5
+ */
6
+ export interface RpcDefinition<S extends Record<string, any>> {
7
+ /** Method name for this RPC (must be unique) */
8
+ method: string;
9
+ /** Schema describing the RPC's data fields */
10
+ schema: S;
11
+ }
12
+ /**
13
+ * Infers the TypeScript type from an RPC schema.
14
+ * @template S The schema type
15
+ */
16
+ export type InferRpcType<S extends Record<string, any>> = {
17
+ [P in keyof S]: S[P] extends {
18
+ read(dv: DataView, o: number): infer R;
19
+ } ? R : never;
20
+ };
21
+ /**
22
+ * Define a type-safe RPC with automatic schema generation.
23
+ *
24
+ * RPCs are bidirectional one-off events/commands for:
25
+ * - Meta-game events (achievements, notifications)
26
+ * - Match lifecycle (countdown, results)
27
+ * - Request/response patterns
28
+ * - System announcements
29
+ *
30
+ * NOT for game state synchronization (use Snapshots) or player inputs (use Intents)
31
+ *
32
+ * @template S The schema type
33
+ * @param definition RPC configuration with method and schema
34
+ * @returns A DefinedRpc object with method and codec
35
+ *
36
+ * @example Server → Client RPC
37
+ * ```ts
38
+ * const MatchCountdown = defineRpc({
39
+ * method: 'matchCountdown',
40
+ * schema: {
41
+ * secondsRemaining: BinaryCodec.u8,
42
+ * }
43
+ * });
44
+ *
45
+ * // Server sends
46
+ * server.sendRpcBroadcast(MatchCountdown, { secondsRemaining: 10 });
47
+ *
48
+ * // Client receives
49
+ * client.onRpc(MatchCountdown, (rpc) => {
50
+ * console.log(`Match starting in ${rpc.secondsRemaining}s`);
51
+ * });
52
+ * ```
53
+ *
54
+ * @example Client → Server RPC
55
+ * ```ts
56
+ * const BuyItem = defineRpc({
57
+ * method: 'buyItem',
58
+ * schema: {
59
+ * itemId: BinaryCodec.string(32),
60
+ * }
61
+ * });
62
+ *
63
+ * // Client sends
64
+ * client.sendRpc(BuyItem, { itemId: 'long_sword' });
65
+ *
66
+ * // Server receives
67
+ * server.onRpc(BuyItem, (peerId, rpc) => {
68
+ * console.log(`${peerId} wants to buy ${rpc.itemId}`);
69
+ * });
70
+ * ```
71
+ */
72
+ export declare function defineRpc<S extends Record<string, any>>(definition: RpcDefinition<S>): DefinedRpc<InferRpcType<S>>;