react-websocket 3.3.0 → 3.6.0

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/core.d.cts CHANGED
@@ -111,14 +111,42 @@ interface SendOptions {
111
111
  volatile?: boolean;
112
112
  /**
113
113
  * Keep only the newest queued message with this key. Sending again with the same key
114
- * while disconnected replaces the earlier one, which moves to the back of the queue.
114
+ * while disconnected replaces the earlier one, which moves to the back of the queue
115
+ * (or of the `priority` messages, for a priority one).
115
116
  * For state where only the latest value matters, such as "my position is now (x, y)".
116
117
  */
117
118
  key?: string;
119
+ /**
120
+ * Queue this message ahead of every message queued without `priority`, after earlier
121
+ * priority messages. For control messages that must not wait behind a backlog, such as
122
+ * cancel, interrupt or a token refresh. Messages sent from `open` listeners still go first.
123
+ *
124
+ * It only matters while the socket isn't open: once a message is handed to the browser,
125
+ * nothing can overtake it. To keep that buffer short, send bulky streams with `maxBuffered`.
126
+ * @default false
127
+ */
128
+ priority?: boolean;
129
+ /**
130
+ * Drop the message instead of sending it while more than this many bytes are already
131
+ * waiting in the browser's send buffer (`socket.bufferedAmount`). For high-rate streams on
132
+ * a slow link, such as audio frames or per-tick input, where a late message is worse than
133
+ * none. `send()` returns `false`, and `onDrop` isn't called.
134
+ *
135
+ * Only checked while the socket is open: a message queued while disconnected is sent on
136
+ * open regardless. `bufferedAmount` includes what you sent earlier in the same task, so set
137
+ * it well above one burst of messages.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * voice.send(frame, { volatile: true, maxBuffered: 64 * 1024 });
142
+ * ```
143
+ */
144
+ maxBuffered?: number;
118
145
  /**
119
146
  * Called if the message is dropped from the queue without being sent: replaced by a
120
147
  * newer message with the same `key`, or evicted by `queue.limit`. Not called when the
121
- * queue is cleared because the client closed or gave up.
148
+ * queue is cleared because the client closed or gave up, or when `send()` drops the
149
+ * message right away (`volatile` while not open, or `maxBuffered`) and returns `false`.
122
150
  */
123
151
  onDrop?: () => void;
124
152
  }
@@ -126,7 +154,7 @@ interface SendOptions {
126
154
  interface QueueOptions {
127
155
  /**
128
156
  * Maximum number of messages held while not connected. When full, the oldest
129
- * message is dropped.
157
+ * message is dropped, skipping `priority` messages unless nothing else is queued.
130
158
  * @default Infinity
131
159
  */
132
160
  limit?: number;
@@ -309,12 +337,14 @@ interface WebSocketClient<In = unknown, Out = unknown> {
309
337
  * Send a message (encoded with `serialize`/`json` if configured). If the socket isn't
310
338
  * open, the message is queued and sent on open (see the `queue` option), unless
311
339
  * `options.volatile` is set.
312
- * @returns `true` if it was sent immediately, `false` if it was queued or dropped.
340
+ * @returns `true` if it was sent immediately, `false` if it was queued or dropped
341
+ * (`volatile` while not open, or `maxBuffered` while open).
313
342
  *
314
343
  * @example
315
344
  * ```ts
316
345
  * game.send({ type: 'input', dx, dy }, { volatile: true }); // never replayed after a reconnect
317
346
  * game.send({ type: 'position', x, y }, { key: 'position' }); // only the newest is queued
347
+ * game.send({ type: 'leave' }, { priority: true }); // ahead of everything already queued
318
348
  * ```
319
349
  */
320
350
  send(message: Out, options?: SendOptions): boolean;
@@ -457,20 +487,36 @@ interface WebSocketRequestsOptions<Req, In, Out> {
457
487
  reply: (message: In) => number | string | null | undefined;
458
488
  /**
459
489
  * Called on a reply. Return something other than `undefined` to reject the request with
460
- * it instead of resolving, e.g. for a server-side error reply.
490
+ * it instead of resolving, e.g. for a server-side error reply. A stream throws it.
461
491
  */
462
492
  error?: (message: In) => unknown;
463
493
  /**
464
- * Default time to wait for a reply, in ms. `Infinity` waits forever.
494
+ * Whether a reply is the last one of a stream. `stream()` needs it. That reply is yielded
495
+ * too, then the stream ends.
496
+ */
497
+ done?: (message: In) => boolean;
498
+ /**
499
+ * Build the message that tells the server to stop working on request `id`. Sent when this
500
+ * side gives up: the request's `signal` aborts, it times out, a loop over a stream exits
501
+ * early, or the manager is disposed. Not sent after the last reply, an error reply, a lost
502
+ * connection or `close()`.
503
+ */
504
+ cancel?: (id: number) => Out;
505
+ /**
506
+ * Default time to wait for a reply, in ms. For a stream, the time to wait for each reply,
507
+ * including the first. `Infinity` waits forever.
465
508
  * @default 10000
466
509
  */
467
510
  timeout?: number;
468
511
  }
469
- /** Per-call options for {@link WebSocketRequests.request}. */
512
+ /** Per-call options for {@link WebSocketRequests.request} and {@link WebSocketRequests.stream}. */
470
513
  interface RequestOptions extends SendOptions {
471
- /** Time to wait for a reply, in ms. Overrides the manager's `timeout`. */
514
+ /**
515
+ * Time to wait for a reply (for a stream, for each reply), in ms. Overrides the manager's
516
+ * `timeout`.
517
+ */
472
518
  timeout?: number;
473
- /** Abort the request. It rejects with `signal.reason`. A late reply is ignored. */
519
+ /** Abort the request. It rejects (a stream throws) with `signal.reason`. A late reply is ignored. */
474
520
  signal?: AbortSignal;
475
521
  }
476
522
  /** Request/response over one client. Create with {@link createWebSocketRequests}. */
@@ -487,7 +533,26 @@ interface WebSocketRequests<Req, In> {
487
533
  * queue (replaced by a newer request with the same `key`, or evicted by `queue.limit`).
488
534
  */
489
535
  request(message: Req, options?: RequestOptions): Promise<In>;
490
- /** Number of requests waiting for a reply. */
536
+ /**
537
+ * Send a request and iterate over its replies, up to and including the one `done` matches.
538
+ * Holds the connection until the stream ends. Needs the `done` option.
539
+ *
540
+ * `timeout` is the time to wait for each reply. Replies that arrive before the loop reads
541
+ * them are buffered. The stream is one-shot: iterate it once.
542
+ *
543
+ * Fails where `request()` rejects, with the same errors, after yielding the replies already
544
+ * received. Aborting `signal` throws `signal.reason` right away and discards them. Leaving
545
+ * the loop early, aborting and timing out send `cancel`.
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * for await (const reply of rpc.stream({ type: 'ask', prompt }, { signal })) {
550
+ * text += reply.delta;
551
+ * }
552
+ * ```
553
+ */
554
+ stream(message: Req, options?: RequestOptions): AsyncIterableIterator<In>;
555
+ /** Number of requests and streams waiting for a reply. */
491
556
  pending(): number;
492
557
  /** Reject every pending request and stop listening. Only needed for managers created dynamically. */
493
558
  dispose(): void;
@@ -649,6 +714,85 @@ interface WebSocketResponder<P = unknown, R = unknown> {
649
714
  */
650
715
  export declare function createWebSocketResponder<Q extends RequestFields, R, In, const Out>(client: WebSocketClient<In, Out>, options: WebSocketResponderOptions<Q, R, NoInfer<In>, NoInfer<Out>>): WebSocketResponder<ParamsOf<Q>, R>;
651
716
  //#endregion
717
+ //#region src/sendrate.d.ts
718
+ /** Options for {@link createWebSocketSendRate}. */
719
+ interface WebSocketSendRateOptions {
720
+ /**
721
+ * Ticks per second while the connection keeps up.
722
+ * @default 30
723
+ */
724
+ maxRate?: number;
725
+ /**
726
+ * The rate never drops below this, in ticks per second.
727
+ * @default 5
728
+ */
729
+ minRate?: number;
730
+ /**
731
+ * Bytes waiting in the browser's send buffer (`socket.bufferedAmount`) above which a tick
732
+ * sends nothing and the rate halves.
733
+ * @default 1024
734
+ */
735
+ maxBuffered?: number;
736
+ /**
737
+ * Measurements from `createWebSocketLatency` on the same client. With them, a round-trip time
738
+ * that rises also lowers the rate. Without them, only the send buffer is watched.
739
+ */
740
+ latency?: Pick<WebSocketLatency, 'getSnapshot'>;
741
+ /**
742
+ * How far `rtt` may rise above the lowest one measured on this connection before the rate
743
+ * halves, in ms. A steady high ping keeps the full rate; only a rising one counts.
744
+ * @default 100
745
+ */
746
+ maxRttIncrease?: number;
747
+ }
748
+ /** The current send rate. A new object is created on every change. */
749
+ interface SendRate {
750
+ /** Ticks per second, rounded. */
751
+ readonly rate: number;
752
+ /**
753
+ * Whether the last tick found the link congested: the send buffer above `maxBuffered`, or
754
+ * `rtt` risen more than `maxRttIncrease`.
755
+ */
756
+ readonly congested: boolean;
757
+ }
758
+ /** Keyed sends coalesced at a rate that adapts to the link. Create with {@link createWebSocketSendRate}. */
759
+ interface WebSocketSendRate<Out> {
760
+ /** The client the messages are sent on. */
761
+ readonly client: WebSocketClient<any, Out>;
762
+ /**
763
+ * Send `message` at the next tick. A message with the same `key` still waiting for it is
764
+ * replaced, so only the newest one goes out. Sent with `volatile`.
765
+ */
766
+ send(message: Out, key: string): void;
767
+ /** Send everything waiting now, e.g. right before a one-shot action that must carry the latest aim. */
768
+ flush(): void;
769
+ /** The current {@link SendRate}. Returns the same object until it changes. */
770
+ getSnapshot(): SendRate;
771
+ /** Called whenever the rate or `congested` changes. Compatible with `useSyncExternalStore`. */
772
+ subscribe(listener: () => void): () => void;
773
+ /** Stop sending and listening. Only needed for instances created dynamically. */
774
+ dispose(): void;
775
+ }
776
+ /**
777
+ * Send high-rate state, such as aim, cursor or position, at a rate that adapts to the
778
+ * connection. Messages are coalesced per key between ticks. The rate drops by half when the
779
+ * send buffer fills up or the round trip grows, and climbs back one tick per second at a time
780
+ * when the link keeps up, so players on a poor connection degrade smoothly instead of building
781
+ * up lag.
782
+ *
783
+ * Ticks run only while the socket is open and something is waiting. Messages waiting during a
784
+ * reconnect go out after it; `close()` drops them. It doesn't open the connection by itself.
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * export const stream = createWebSocketSendRate(game, { latency, maxRate: 30 });
789
+ *
790
+ * // On every pointer move: at most 30 a second, fewer on a congested link.
791
+ * stream.send({ type: 'aim', angle }, 'aim');
792
+ * ```
793
+ */
794
+ export declare function createWebSocketSendRate<Out>(client: WebSocketClient<any, Out>, options?: WebSocketSendRateOptions): WebSocketSendRate<Out>;
795
+ //#endregion
652
796
  //#region src/state.d.ts
653
797
  /** Options for {@link createWebSocketState}. */
654
798
  interface WebSocketStateOptions<S, In> {
@@ -659,6 +803,17 @@ interface WebSocketStateOptions<S, In> {
659
803
  * change, or the same `state` to ignore the message (subscribers aren't notified).
660
804
  */
661
805
  reduce: (state: S, message: In) => S;
806
+ /**
807
+ * `'frame'`: notify subscribers at most once per animation frame instead of once per
808
+ * message, so a fast stream (tokens, tickers, game state) renders once per frame.
809
+ * `getState()` is always current; only notifications wait. `setState` and `reset` still
810
+ * notify right away.
811
+ *
812
+ * Browsers pause animation frames in background tabs: the state keeps up, and subscribers
813
+ * hear about it when the tab is visible again. Without `requestAnimationFrame` (Node, some
814
+ * workers), it waits about 16 ms instead.
815
+ */
816
+ batch?: 'frame';
662
817
  }
663
818
  /**
664
819
  * State derived from a client's messages. Read it with `useWebSocketState(store, selector)`,
@@ -748,5 +903,5 @@ interface WebSocketTopics {
748
903
  */
749
904
  export declare function createWebSocketTopics<const Out>(client: WebSocketClient<any, Out>, options: WebSocketTopicsOptions<NoInfer<Out>>): WebSocketTopics;
750
905
  //#endregion
751
- export type { CloseInfo, EventMap, HeartbeatOptions, IncomingRequest, Latency, QueueOptions, ReconnectOptions, RequestHandler, RequestOptions, SendData, SendOptions, Snapshot, StandardSchemaV1, Status, UrlSource, WebSocketClient, WebSocketLatency, WebSocketLatencyOptions, WebSocketOptions, WebSocketRequests, WebSocketRequestsOptions, WebSocketResponder, WebSocketResponderOptions, WebSocketState, WebSocketStateOptions, WebSocketTopics, WebSocketTopicsOptions };
906
+ export type { CloseInfo, EventMap, HeartbeatOptions, IncomingRequest, Latency, QueueOptions, ReconnectOptions, RequestHandler, RequestOptions, SendData, SendOptions, SendRate, Snapshot, StandardSchemaV1, Status, UrlSource, WebSocketClient, WebSocketLatency, WebSocketLatencyOptions, WebSocketOptions, WebSocketRequests, WebSocketRequestsOptions, WebSocketResponder, WebSocketResponderOptions, WebSocketSendRate, WebSocketSendRateOptions, WebSocketState, WebSocketStateOptions, WebSocketTopics, WebSocketTopicsOptions };
752
907
  //# sourceMappingURL=core.d.cts.map
package/dist/core.d.ts CHANGED
@@ -111,14 +111,42 @@ interface SendOptions {
111
111
  volatile?: boolean;
112
112
  /**
113
113
  * Keep only the newest queued message with this key. Sending again with the same key
114
- * while disconnected replaces the earlier one, which moves to the back of the queue.
114
+ * while disconnected replaces the earlier one, which moves to the back of the queue
115
+ * (or of the `priority` messages, for a priority one).
115
116
  * For state where only the latest value matters, such as "my position is now (x, y)".
116
117
  */
117
118
  key?: string;
119
+ /**
120
+ * Queue this message ahead of every message queued without `priority`, after earlier
121
+ * priority messages. For control messages that must not wait behind a backlog, such as
122
+ * cancel, interrupt or a token refresh. Messages sent from `open` listeners still go first.
123
+ *
124
+ * It only matters while the socket isn't open: once a message is handed to the browser,
125
+ * nothing can overtake it. To keep that buffer short, send bulky streams with `maxBuffered`.
126
+ * @default false
127
+ */
128
+ priority?: boolean;
129
+ /**
130
+ * Drop the message instead of sending it while more than this many bytes are already
131
+ * waiting in the browser's send buffer (`socket.bufferedAmount`). For high-rate streams on
132
+ * a slow link, such as audio frames or per-tick input, where a late message is worse than
133
+ * none. `send()` returns `false`, and `onDrop` isn't called.
134
+ *
135
+ * Only checked while the socket is open: a message queued while disconnected is sent on
136
+ * open regardless. `bufferedAmount` includes what you sent earlier in the same task, so set
137
+ * it well above one burst of messages.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * voice.send(frame, { volatile: true, maxBuffered: 64 * 1024 });
142
+ * ```
143
+ */
144
+ maxBuffered?: number;
118
145
  /**
119
146
  * Called if the message is dropped from the queue without being sent: replaced by a
120
147
  * newer message with the same `key`, or evicted by `queue.limit`. Not called when the
121
- * queue is cleared because the client closed or gave up.
148
+ * queue is cleared because the client closed or gave up, or when `send()` drops the
149
+ * message right away (`volatile` while not open, or `maxBuffered`) and returns `false`.
122
150
  */
123
151
  onDrop?: () => void;
124
152
  }
@@ -126,7 +154,7 @@ interface SendOptions {
126
154
  interface QueueOptions {
127
155
  /**
128
156
  * Maximum number of messages held while not connected. When full, the oldest
129
- * message is dropped.
157
+ * message is dropped, skipping `priority` messages unless nothing else is queued.
130
158
  * @default Infinity
131
159
  */
132
160
  limit?: number;
@@ -309,12 +337,14 @@ interface WebSocketClient<In = unknown, Out = unknown> {
309
337
  * Send a message (encoded with `serialize`/`json` if configured). If the socket isn't
310
338
  * open, the message is queued and sent on open (see the `queue` option), unless
311
339
  * `options.volatile` is set.
312
- * @returns `true` if it was sent immediately, `false` if it was queued or dropped.
340
+ * @returns `true` if it was sent immediately, `false` if it was queued or dropped
341
+ * (`volatile` while not open, or `maxBuffered` while open).
313
342
  *
314
343
  * @example
315
344
  * ```ts
316
345
  * game.send({ type: 'input', dx, dy }, { volatile: true }); // never replayed after a reconnect
317
346
  * game.send({ type: 'position', x, y }, { key: 'position' }); // only the newest is queued
347
+ * game.send({ type: 'leave' }, { priority: true }); // ahead of everything already queued
318
348
  * ```
319
349
  */
320
350
  send(message: Out, options?: SendOptions): boolean;
@@ -457,20 +487,36 @@ interface WebSocketRequestsOptions<Req, In, Out> {
457
487
  reply: (message: In) => number | string | null | undefined;
458
488
  /**
459
489
  * Called on a reply. Return something other than `undefined` to reject the request with
460
- * it instead of resolving, e.g. for a server-side error reply.
490
+ * it instead of resolving, e.g. for a server-side error reply. A stream throws it.
461
491
  */
462
492
  error?: (message: In) => unknown;
463
493
  /**
464
- * Default time to wait for a reply, in ms. `Infinity` waits forever.
494
+ * Whether a reply is the last one of a stream. `stream()` needs it. That reply is yielded
495
+ * too, then the stream ends.
496
+ */
497
+ done?: (message: In) => boolean;
498
+ /**
499
+ * Build the message that tells the server to stop working on request `id`. Sent when this
500
+ * side gives up: the request's `signal` aborts, it times out, a loop over a stream exits
501
+ * early, or the manager is disposed. Not sent after the last reply, an error reply, a lost
502
+ * connection or `close()`.
503
+ */
504
+ cancel?: (id: number) => Out;
505
+ /**
506
+ * Default time to wait for a reply, in ms. For a stream, the time to wait for each reply,
507
+ * including the first. `Infinity` waits forever.
465
508
  * @default 10000
466
509
  */
467
510
  timeout?: number;
468
511
  }
469
- /** Per-call options for {@link WebSocketRequests.request}. */
512
+ /** Per-call options for {@link WebSocketRequests.request} and {@link WebSocketRequests.stream}. */
470
513
  interface RequestOptions extends SendOptions {
471
- /** Time to wait for a reply, in ms. Overrides the manager's `timeout`. */
514
+ /**
515
+ * Time to wait for a reply (for a stream, for each reply), in ms. Overrides the manager's
516
+ * `timeout`.
517
+ */
472
518
  timeout?: number;
473
- /** Abort the request. It rejects with `signal.reason`. A late reply is ignored. */
519
+ /** Abort the request. It rejects (a stream throws) with `signal.reason`. A late reply is ignored. */
474
520
  signal?: AbortSignal;
475
521
  }
476
522
  /** Request/response over one client. Create with {@link createWebSocketRequests}. */
@@ -487,7 +533,26 @@ interface WebSocketRequests<Req, In> {
487
533
  * queue (replaced by a newer request with the same `key`, or evicted by `queue.limit`).
488
534
  */
489
535
  request(message: Req, options?: RequestOptions): Promise<In>;
490
- /** Number of requests waiting for a reply. */
536
+ /**
537
+ * Send a request and iterate over its replies, up to and including the one `done` matches.
538
+ * Holds the connection until the stream ends. Needs the `done` option.
539
+ *
540
+ * `timeout` is the time to wait for each reply. Replies that arrive before the loop reads
541
+ * them are buffered. The stream is one-shot: iterate it once.
542
+ *
543
+ * Fails where `request()` rejects, with the same errors, after yielding the replies already
544
+ * received. Aborting `signal` throws `signal.reason` right away and discards them. Leaving
545
+ * the loop early, aborting and timing out send `cancel`.
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * for await (const reply of rpc.stream({ type: 'ask', prompt }, { signal })) {
550
+ * text += reply.delta;
551
+ * }
552
+ * ```
553
+ */
554
+ stream(message: Req, options?: RequestOptions): AsyncIterableIterator<In>;
555
+ /** Number of requests and streams waiting for a reply. */
491
556
  pending(): number;
492
557
  /** Reject every pending request and stop listening. Only needed for managers created dynamically. */
493
558
  dispose(): void;
@@ -649,6 +714,85 @@ interface WebSocketResponder<P = unknown, R = unknown> {
649
714
  */
650
715
  export declare function createWebSocketResponder<Q extends RequestFields, R, In, const Out>(client: WebSocketClient<In, Out>, options: WebSocketResponderOptions<Q, R, NoInfer<In>, NoInfer<Out>>): WebSocketResponder<ParamsOf<Q>, R>;
651
716
  //#endregion
717
+ //#region src/sendrate.d.ts
718
+ /** Options for {@link createWebSocketSendRate}. */
719
+ interface WebSocketSendRateOptions {
720
+ /**
721
+ * Ticks per second while the connection keeps up.
722
+ * @default 30
723
+ */
724
+ maxRate?: number;
725
+ /**
726
+ * The rate never drops below this, in ticks per second.
727
+ * @default 5
728
+ */
729
+ minRate?: number;
730
+ /**
731
+ * Bytes waiting in the browser's send buffer (`socket.bufferedAmount`) above which a tick
732
+ * sends nothing and the rate halves.
733
+ * @default 1024
734
+ */
735
+ maxBuffered?: number;
736
+ /**
737
+ * Measurements from `createWebSocketLatency` on the same client. With them, a round-trip time
738
+ * that rises also lowers the rate. Without them, only the send buffer is watched.
739
+ */
740
+ latency?: Pick<WebSocketLatency, 'getSnapshot'>;
741
+ /**
742
+ * How far `rtt` may rise above the lowest one measured on this connection before the rate
743
+ * halves, in ms. A steady high ping keeps the full rate; only a rising one counts.
744
+ * @default 100
745
+ */
746
+ maxRttIncrease?: number;
747
+ }
748
+ /** The current send rate. A new object is created on every change. */
749
+ interface SendRate {
750
+ /** Ticks per second, rounded. */
751
+ readonly rate: number;
752
+ /**
753
+ * Whether the last tick found the link congested: the send buffer above `maxBuffered`, or
754
+ * `rtt` risen more than `maxRttIncrease`.
755
+ */
756
+ readonly congested: boolean;
757
+ }
758
+ /** Keyed sends coalesced at a rate that adapts to the link. Create with {@link createWebSocketSendRate}. */
759
+ interface WebSocketSendRate<Out> {
760
+ /** The client the messages are sent on. */
761
+ readonly client: WebSocketClient<any, Out>;
762
+ /**
763
+ * Send `message` at the next tick. A message with the same `key` still waiting for it is
764
+ * replaced, so only the newest one goes out. Sent with `volatile`.
765
+ */
766
+ send(message: Out, key: string): void;
767
+ /** Send everything waiting now, e.g. right before a one-shot action that must carry the latest aim. */
768
+ flush(): void;
769
+ /** The current {@link SendRate}. Returns the same object until it changes. */
770
+ getSnapshot(): SendRate;
771
+ /** Called whenever the rate or `congested` changes. Compatible with `useSyncExternalStore`. */
772
+ subscribe(listener: () => void): () => void;
773
+ /** Stop sending and listening. Only needed for instances created dynamically. */
774
+ dispose(): void;
775
+ }
776
+ /**
777
+ * Send high-rate state, such as aim, cursor or position, at a rate that adapts to the
778
+ * connection. Messages are coalesced per key between ticks. The rate drops by half when the
779
+ * send buffer fills up or the round trip grows, and climbs back one tick per second at a time
780
+ * when the link keeps up, so players on a poor connection degrade smoothly instead of building
781
+ * up lag.
782
+ *
783
+ * Ticks run only while the socket is open and something is waiting. Messages waiting during a
784
+ * reconnect go out after it; `close()` drops them. It doesn't open the connection by itself.
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * export const stream = createWebSocketSendRate(game, { latency, maxRate: 30 });
789
+ *
790
+ * // On every pointer move: at most 30 a second, fewer on a congested link.
791
+ * stream.send({ type: 'aim', angle }, 'aim');
792
+ * ```
793
+ */
794
+ export declare function createWebSocketSendRate<Out>(client: WebSocketClient<any, Out>, options?: WebSocketSendRateOptions): WebSocketSendRate<Out>;
795
+ //#endregion
652
796
  //#region src/state.d.ts
653
797
  /** Options for {@link createWebSocketState}. */
654
798
  interface WebSocketStateOptions<S, In> {
@@ -659,6 +803,17 @@ interface WebSocketStateOptions<S, In> {
659
803
  * change, or the same `state` to ignore the message (subscribers aren't notified).
660
804
  */
661
805
  reduce: (state: S, message: In) => S;
806
+ /**
807
+ * `'frame'`: notify subscribers at most once per animation frame instead of once per
808
+ * message, so a fast stream (tokens, tickers, game state) renders once per frame.
809
+ * `getState()` is always current; only notifications wait. `setState` and `reset` still
810
+ * notify right away.
811
+ *
812
+ * Browsers pause animation frames in background tabs: the state keeps up, and subscribers
813
+ * hear about it when the tab is visible again. Without `requestAnimationFrame` (Node, some
814
+ * workers), it waits about 16 ms instead.
815
+ */
816
+ batch?: 'frame';
662
817
  }
663
818
  /**
664
819
  * State derived from a client's messages. Read it with `useWebSocketState(store, selector)`,
@@ -748,5 +903,5 @@ interface WebSocketTopics {
748
903
  */
749
904
  export declare function createWebSocketTopics<const Out>(client: WebSocketClient<any, Out>, options: WebSocketTopicsOptions<NoInfer<Out>>): WebSocketTopics;
750
905
  //#endregion
751
- export type { CloseInfo, EventMap, HeartbeatOptions, IncomingRequest, Latency, QueueOptions, ReconnectOptions, RequestHandler, RequestOptions, SendData, SendOptions, Snapshot, StandardSchemaV1, Status, UrlSource, WebSocketClient, WebSocketLatency, WebSocketLatencyOptions, WebSocketOptions, WebSocketRequests, WebSocketRequestsOptions, WebSocketResponder, WebSocketResponderOptions, WebSocketState, WebSocketStateOptions, WebSocketTopics, WebSocketTopicsOptions };
906
+ export type { CloseInfo, EventMap, HeartbeatOptions, IncomingRequest, Latency, QueueOptions, ReconnectOptions, RequestHandler, RequestOptions, SendData, SendOptions, SendRate, Snapshot, StandardSchemaV1, Status, UrlSource, WebSocketClient, WebSocketLatency, WebSocketLatencyOptions, WebSocketOptions, WebSocketRequests, WebSocketRequestsOptions, WebSocketResponder, WebSocketResponderOptions, WebSocketSendRate, WebSocketSendRateOptions, WebSocketState, WebSocketStateOptions, WebSocketTopics, WebSocketTopicsOptions };
752
907
  //# sourceMappingURL=core.d.ts.map