@replit/river 0.15.1 → 0.15.2

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.
Files changed (35) hide show
  1. package/README.md +41 -22
  2. package/dist/{builder-660d3140.d.ts → builder-ebd945c0.d.ts} +1 -1
  3. package/dist/{chunk-O6YQ3JAH.js → chunk-B7VTDQR7.js} +147 -37
  4. package/dist/{chunk-MNWOTQWX.js → chunk-UJHTHOTT.js} +1 -1
  5. package/dist/{chunk-5TX4BKAD.js → chunk-ZRB6IKPV.js} +1 -1
  6. package/dist/{connection-162c0f7b.d.ts → connection-10a24478.d.ts} +1 -1
  7. package/dist/{connection-93daccc3.d.ts → connection-f4492948.d.ts} +1 -1
  8. package/dist/{index-76b801f8.d.ts → index-bbccacef.d.ts} +84 -11
  9. package/dist/router/index.d.cts +3 -3
  10. package/dist/router/index.d.ts +3 -3
  11. package/dist/transport/impls/uds/client.cjs +197 -88
  12. package/dist/transport/impls/uds/client.d.cts +3 -3
  13. package/dist/transport/impls/uds/client.d.ts +3 -3
  14. package/dist/transport/impls/uds/client.js +2 -2
  15. package/dist/transport/impls/uds/server.cjs +66 -69
  16. package/dist/transport/impls/uds/server.d.cts +3 -3
  17. package/dist/transport/impls/uds/server.d.ts +3 -3
  18. package/dist/transport/impls/uds/server.js +2 -2
  19. package/dist/transport/impls/ws/client.cjs +197 -90
  20. package/dist/transport/impls/ws/client.d.cts +3 -3
  21. package/dist/transport/impls/ws/client.d.ts +3 -3
  22. package/dist/transport/impls/ws/client.js +2 -2
  23. package/dist/transport/impls/ws/server.cjs +66 -69
  24. package/dist/transport/impls/ws/server.d.cts +3 -3
  25. package/dist/transport/impls/ws/server.d.ts +3 -3
  26. package/dist/transport/impls/ws/server.js +2 -2
  27. package/dist/transport/index.cjs +197 -90
  28. package/dist/transport/index.d.cts +1 -1
  29. package/dist/transport/index.d.ts +1 -1
  30. package/dist/transport/index.js +1 -1
  31. package/dist/util/testHelpers.cjs +61 -64
  32. package/dist/util/testHelpers.d.cts +10 -4
  33. package/dist/util/testHelpers.d.ts +10 -4
  34. package/dist/util/testHelpers.js +13 -5
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -1,36 +1,60 @@
1
1
  # River
2
2
 
3
- ## Long-lived Streaming Remote Procedure Calls
3
+ River allows multiple clients to connect to and make remote procedure calls to a remote server as if they were local procedures.
4
4
 
5
- It's like tRPC/gRPC but with
5
+ ## Long-lived streaming remote procedure calls
6
+
7
+ River provides a framework for long-lived streaming Remote Procedure Calls (RPCs) in modern web applications, featuring advanced error handling and customizable retry policies to ensure seamless communication between clients and servers.
8
+
9
+ River provides a framework similar to [tRPC](https://trpc.io/) and [gRPC](https://grpc.io/) but with additional features:
6
10
 
7
11
  - JSON Schema Support + run-time schema validation
8
12
  - full-duplex streaming
9
13
  - service multiplexing
10
14
  - result types and error handling
11
- - snappy DX (no code-generation)
15
+ - snappy DX (no code generation)
12
16
  - transparent reconnect support for long-lived sessions
13
17
  - over any transport (WebSockets and Unix Domain Socket out of the box)
14
18
 
15
19
  See [PROTOCOL.md](./PROTOCOL.md) for more information on the protocol.
16
20
 
17
- ## Installation
21
+ ### Prerequisites
22
+
23
+ Before proceeding, ensure you have TypeScript 5 installed and configured appropriately:
24
+
25
+ 1. **Ensure `"moduleResolution": "bundler"` in tsconfig.json**:
26
+
27
+ ```json
28
+ {
29
+ "compilerOptions": {
30
+ "moduleResolution": "bundler"
31
+ // Other compiler options...
32
+ }
33
+ }
34
+ ```
18
35
 
19
- To use River, you must be on least Typescript 5 with `"moduleResolution": "bundler"`.
36
+ If it exists but is set to a different value, modify it to `"bundler"`.
37
+
38
+ 2. Install River and Dependencies:
39
+
40
+ To use River, install the required packages using npm:
20
41
 
21
42
  ```bash
22
- npm i @replit/river @sinclair/typebox
43
+ npm i @replit/river @sinclair/typebox
44
+ ```
23
45
 
24
- # if you plan on using WebSocket for transport, also install
46
+ 3. If you plan on using WebSocket for the underlying transport, also install
47
+
48
+ ```bash
25
49
  npm i ws isomorphic-ws
26
50
  ```
27
51
 
28
- ## Writing Services
52
+ ## Writing services
29
53
 
30
54
  ### Concepts
31
55
 
32
56
  - Router: a collection of services, namespaced by service name.
33
- - Service: a collection of procedures with shared state.
57
+ - Service: a collection of procedures with a shared state.
34
58
  - Procedure: a single procedure. A procedure declares its type, an input message type, an output message type, optionally an error type, and the associated handler. Valid types are:
35
59
  - `rpc` whose handler has a signature of `Input -> Result<Output, Error>`.
36
60
  - `upload` whose handler has a signature of `AsyncIterableIterator<Input> -> Result<Output, Error>`.
@@ -73,7 +97,7 @@ export const ExampleServiceConstructor = () =>
73
97
  export const serviceDefs = buildServiceDefs([ExampleServiceConstructor()]);
74
98
  ```
75
99
 
76
- Then, we create the server
100
+ Then, we create the server:
77
101
 
78
102
  ```ts
79
103
  import http from 'http';
@@ -134,20 +158,16 @@ bindLogger(console.log);
134
158
  setLevel('info');
135
159
  ```
136
160
 
137
- ### Connection Status
161
+ ### Connection status
138
162
 
139
- River define two types of reconnects:
163
+ River defines two types of reconnects:
140
164
 
141
- 1. Transparent reconnects: we lost the connection temporarily and reconnected without losing any messages. To the application level, nothing happened.
142
- 2. Hard reconnect: we've lost all server state and the client should setup the world again.
165
+ 1. **Transparent reconnects:** These occur when the connection is temporarily lost and reestablished without losing any messages. From the application's perspective, this process is seamless and does not disrupt ongoing operations.
166
+ 2. **Hard reconnect:** This occurs when all server state is lost, requiring the client to reinitialize anything stateful (e.g. subscriptions).
143
167
 
144
- We can listen for transparent reconnects via the `connectionStatus` events but realistically
145
- no applications should need to listen for this unless it is for debug purposes. Hard reconnects
146
- are signalled via `sessionStatus` events.
168
+ You can listen for transparent reconnects via the `connectionStatus` events, but realistically, no applications should need to listen for this unless it is for debugging purposes. Hard reconnects are signaled via `sessionStatus` events.
147
169
 
148
- If your application is stateful on either the server or the client, the service consumer _should_
149
- wrap all the client-side setup with `transport.addEventListener('sessionStatus', (evt) => ...)` to
150
- do appropriate setup and teardown.
170
+ If your application is stateful on either the server or the client, the service consumer _should_ wrap all the client-side setup with `transport.addEventListener('sessionStatus', (evt) => ...)` to do appropriate setup and teardown.
151
171
 
152
172
  ```ts
153
173
  transport.addEventListener('connectionStatus', (evt) => {
@@ -169,8 +189,7 @@ transport.addEventListener('sessionStatus', (evt) => {
169
189
 
170
190
  ### Further examples
171
191
 
172
- We've also provided an end-to-end testing environment using Next.js, and a simple backend connected
173
- with the WebSocket transport that you can [play with on Replit](https://replit.com/@jzhao-replit/riverbed).
192
+ We've also provided an end-to-end testing environment using `Next.js`, and a simple backend connected with the WebSocket transport that you can [play with on Replit](https://replit.com/@jzhao-replit/riverbed).
174
193
 
175
194
  You can find more service examples in the [E2E test fixtures](https://github.com/replit/river/blob/main/__tests__/fixtures/services.ts)
176
195
 
@@ -1,6 +1,6 @@
1
1
  import { TObject, TUnion, TString, TSchema, TNever, TLiteral, Static } from '@sinclair/typebox';
2
2
  import { Pushable } from 'it-pushable';
3
- import { b as TransportClientId, d as Session, C as Connection } from './index-76b801f8.js';
3
+ import { b as TransportClientId, e as Session, C as Connection } from './index-bbccacef.js';
4
4
 
5
5
  /**
6
6
  * The context for services/procedures. This is used only on
@@ -58,15 +58,6 @@ var Connection = class {
58
58
  this.debugId = `conn-${unsafeId()}`;
59
59
  }
60
60
  };
61
- var HEARTBEAT_INTERVAL_MS = 1e3;
62
- var HEARTBEATS_TILL_DEAD = 2;
63
- var SESSION_DISCONNECT_GRACE_MS = 5e3;
64
- var defaultSessionOptions = {
65
- heartbeatIntervalMs: HEARTBEAT_INTERVAL_MS,
66
- heartbeatsUntilDead: HEARTBEATS_TILL_DEAD,
67
- sessionDisconnectGraceMs: SESSION_DISCONNECT_GRACE_MS,
68
- codec: NaiveJsonCodec
69
- };
70
61
  var Session = class {
71
62
  codec;
72
63
  options;
@@ -253,13 +244,95 @@ var Session = class {
253
244
  // transport/transport.ts
254
245
  import { Value } from "@sinclair/typebox/value";
255
246
  import { nanoid as nanoid2 } from "nanoid";
256
- var RECONNECT_JITTER_MAX_MS = 500;
257
- var RECONNECT_INTERVAL_MS = 250;
247
+
248
+ // transport/rateLimit.ts
249
+ var LeakyBucketRateLimit = class {
250
+ budgetConsumed;
251
+ intervalHandles;
252
+ options;
253
+ constructor(options) {
254
+ this.options = options;
255
+ this.budgetConsumed = /* @__PURE__ */ new Map();
256
+ this.intervalHandles = /* @__PURE__ */ new Map();
257
+ }
258
+ getBackoffMs(user) {
259
+ if (!this.budgetConsumed.has(user))
260
+ return 0;
261
+ const exponent = Math.max(0, this.getBudgetConsumed(user) - 1);
262
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
263
+ const backoffMs = Math.min(
264
+ this.options.baseIntervalMs * 2 ** exponent,
265
+ this.options.maxBackoffMs
266
+ );
267
+ return backoffMs + jitter;
268
+ }
269
+ get totalBudgetRestoreTime() {
270
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
271
+ }
272
+ consumeBudget(user) {
273
+ this.stopLeak(user);
274
+ this.budgetConsumed.set(user, this.getBudgetConsumed(user) + 1);
275
+ }
276
+ getBudgetConsumed(user) {
277
+ return this.budgetConsumed.get(user) ?? 0;
278
+ }
279
+ hasBudget(user) {
280
+ return this.getBudgetConsumed(user) < this.options.attemptBudgetCapacity;
281
+ }
282
+ startRestoringBudget(user) {
283
+ if (this.intervalHandles.has(user)) {
284
+ return;
285
+ }
286
+ const restoreBudgetForUser = () => {
287
+ const currentBudget = this.budgetConsumed.get(user);
288
+ if (!currentBudget) {
289
+ this.stopLeak(user);
290
+ return;
291
+ }
292
+ const newBudget = currentBudget - 1;
293
+ if (newBudget === 0) {
294
+ this.budgetConsumed.delete(user);
295
+ return;
296
+ }
297
+ this.budgetConsumed.set(user, newBudget);
298
+ };
299
+ restoreBudgetForUser();
300
+ const intervalHandle = setInterval(
301
+ restoreBudgetForUser,
302
+ this.options.budgetRestoreIntervalMs
303
+ );
304
+ this.intervalHandles.set(user, intervalHandle);
305
+ }
306
+ stopLeak(user) {
307
+ if (!this.intervalHandles.has(user)) {
308
+ return;
309
+ }
310
+ clearInterval(this.intervalHandles.get(user));
311
+ this.intervalHandles.delete(user);
312
+ }
313
+ close() {
314
+ for (const user of this.intervalHandles.keys()) {
315
+ this.stopLeak(user);
316
+ }
317
+ }
318
+ };
319
+
320
+ // transport/transport.ts
258
321
  var defaultTransportOptions = {
259
- retryIntervalMs: RECONNECT_INTERVAL_MS,
260
- retryJitterMs: RECONNECT_JITTER_MAX_MS,
261
- retryAttemptsMax: 5,
262
- ...defaultSessionOptions
322
+ heartbeatIntervalMs: 1e3,
323
+ heartbeatsUntilDead: 2,
324
+ sessionDisconnectGraceMs: 5e3,
325
+ codec: NaiveJsonCodec
326
+ };
327
+ var defaultClientTransportOptions = {
328
+ connectionRetryOptions: {
329
+ baseIntervalMs: 250,
330
+ maxJitterMs: 200,
331
+ maxBackoffMs: 32e3,
332
+ attemptBudgetCapacity: 15,
333
+ budgetRestoreIntervalMs: 200
334
+ },
335
+ ...defaultTransportOptions
263
336
  };
264
337
  var Transport = class {
265
338
  /**
@@ -534,14 +607,26 @@ var Transport = class {
534
607
  }
535
608
  };
536
609
  var ClientTransport = class extends Transport {
610
+ /**
611
+ * The options for this transport.
612
+ */
613
+ options;
537
614
  /**
538
615
  * The map of reconnect promises for each client ID.
539
616
  */
540
617
  inflightConnectionPromises;
618
+ retryBudget;
541
619
  tryReconnecting = true;
542
620
  constructor(clientId, providedOptions) {
543
621
  super(clientId, providedOptions);
622
+ this.options = {
623
+ ...defaultClientTransportOptions,
624
+ ...providedOptions
625
+ };
544
626
  this.inflightConnectionPromises = /* @__PURE__ */ new Map();
627
+ this.retryBudget = new LeakyBucketRateLimit(
628
+ this.options.connectionRetryOptions
629
+ );
545
630
  }
546
631
  handleConnection(conn, to) {
547
632
  if (this.state !== "open")
@@ -572,7 +657,10 @@ var ClientTransport = class extends Transport {
572
657
  log?.info(
573
658
  `${this.clientId} -- connection (id: ${conn.debugId}) to ${to} disconnected`
574
659
  );
575
- void this.connect(to);
660
+ this.inflightConnectionPromises.delete(to);
661
+ if (this.tryReconnecting) {
662
+ void this.connect(to);
663
+ }
576
664
  });
577
665
  conn.addErrorListener((err) => {
578
666
  log?.warn(
@@ -623,41 +711,64 @@ var ClientTransport = class extends Transport {
623
711
  * Manually attempts to connect to a client.
624
712
  * @param to The client ID of the node to connect to.
625
713
  */
626
- async connect(to, attempt = 0) {
627
- if (this.state !== "open" || !this.tryReconnecting) {
714
+ async connect(to) {
715
+ const canProceedWithConnection = () => this.state === "open";
716
+ if (!canProceedWithConnection()) {
628
717
  log?.info(
629
- `${this.clientId} -- transport state is no longer open, not attempting connection`
718
+ `${this.clientId} -- transport state is no longer open, cancelling attempt to connect to ${to}`
630
719
  );
631
720
  return;
632
721
  }
633
722
  let reconnectPromise = this.inflightConnectionPromises.get(to);
634
723
  if (!reconnectPromise) {
635
- reconnectPromise = this.createNewOutgoingConnection(to).then((conn) => {
724
+ log?.info(`${this.clientId} -- attempting connection to ${to}`);
725
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
726
+ if (!this.retryBudget.hasBudget(to)) {
727
+ const errMsg = `not attempting to connect to ${to}, retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
728
+ log?.warn(`${this.clientId} -- ${errMsg}`);
729
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
730
+ return;
731
+ }
732
+ let sleep = Promise.resolve();
733
+ const backoffMs = this.retryBudget.getBackoffMs(to);
734
+ if (backoffMs > 0) {
735
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
736
+ }
737
+ this.retryBudget.consumeBudget(to);
738
+ reconnectPromise = sleep.then(() => {
739
+ if (!canProceedWithConnection()) {
740
+ throw new Error("transport state is no longer open");
741
+ }
742
+ }).then(() => this.createNewOutgoingConnection(to)).then((conn) => {
743
+ if (!canProceedWithConnection()) {
744
+ log?.info(
745
+ `${this.clientId} -- transport state is no longer open, closing pre-handshake connection (id: ${conn.debugId}) to ${to}`
746
+ );
747
+ conn.close();
748
+ throw new Error("transport state is no longer open");
749
+ }
750
+ this.retryBudget.startRestoringBudget(to);
636
751
  this.sendHandshake(to, conn);
637
752
  return conn;
638
753
  });
639
754
  this.inflightConnectionPromises.set(to, reconnectPromise);
755
+ } else {
756
+ log?.info(
757
+ `${this.clientId} -- attempting connection to ${to} (reusing previous attempt)`
758
+ );
640
759
  }
641
760
  try {
642
761
  await reconnectPromise;
643
762
  } catch (error) {
644
- const errStr = coerceErrorString(error);
645
763
  this.inflightConnectionPromises.delete(to);
646
- const shouldRetry = this.state === "open" && this.tryReconnecting;
647
- if (!shouldRetry)
648
- return;
649
- if (attempt >= this.options.retryAttemptsMax) {
650
- const errMsg = `connection to ${to} failed after ${attempt} attempts (${errStr}), giving up`;
651
- log?.error(`${this.clientId} -- ${errMsg}`);
652
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
653
- return;
764
+ const errStr = coerceErrorString(error);
765
+ if (!this.tryReconnecting || !canProceedWithConnection()) {
766
+ log?.warn(`${this.clientId} -- connection to ${to} failed (${errStr})`);
654
767
  } else {
655
- const jitter = Math.floor(Math.random() * this.options.retryJitterMs);
656
- const backoffMs = this.options.retryIntervalMs * 2 ** attempt + jitter;
657
768
  log?.warn(
658
- `${this.clientId} -- connection to ${to} failed (${errStr}), trying again in ${backoffMs}ms`
769
+ `${this.clientId} -- connection to ${to} failed (${errStr}), retrying`
659
770
  );
660
- setTimeout(() => void this.connect(to, attempt + 1), backoffMs);
771
+ return this.connect(to);
661
772
  }
662
773
  }
663
774
  }
@@ -670,9 +781,9 @@ var ClientTransport = class extends Transport {
670
781
  log?.debug(`${this.clientId} -- sending handshake request to ${to}`);
671
782
  conn.send(this.codec.toBuffer(requestMsg));
672
783
  }
673
- onDisconnect(conn, session) {
674
- this.inflightConnectionPromises.delete(session.to);
675
- super.onDisconnect(conn, session);
784
+ close() {
785
+ this.retryBudget.close();
786
+ super.close();
676
787
  }
677
788
  };
678
789
  var ServerTransport = class extends Transport {
@@ -788,7 +899,6 @@ var ServerTransport = class extends Transport {
788
899
  export {
789
900
  ProtocolError,
790
901
  Connection,
791
- defaultSessionOptions,
792
902
  Session,
793
903
  Transport,
794
904
  ClientTransport,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Connection
3
- } from "./chunk-O6YQ3JAH.js";
3
+ } from "./chunk-B7VTDQR7.js";
4
4
 
5
5
  // transport/transforms/messageFraming.ts
6
6
  import { Transform } from "node:stream";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Connection
3
- } from "./chunk-O6YQ3JAH.js";
3
+ } from "./chunk-B7VTDQR7.js";
4
4
 
5
5
  // transport/impls/ws/connection.ts
6
6
  var WebSocketConnection = class extends Connection {
@@ -1,4 +1,4 @@
1
- import { C as Connection } from './index-76b801f8.js';
1
+ import { C as Connection } from './index-bbccacef.js';
2
2
  import { Socket } from 'node:net';
3
3
  import stream, { Transform, TransformCallback, TransformOptions } from 'node:stream';
4
4
 
@@ -1,5 +1,5 @@
1
1
  import WebSocket from 'isomorphic-ws';
2
- import { C as Connection } from './index-76b801f8.js';
2
+ import { C as Connection } from './index-bbccacef.js';
3
3
 
4
4
  declare class WebSocketConnection extends Connection {
5
5
  ws: WebSocket;
@@ -129,9 +129,22 @@ declare abstract class Connection {
129
129
  abstract close(): void;
130
130
  }
131
131
  interface SessionOptions {
132
+ /**
133
+ * Frequency at which to send heartbeat acknowledgements
134
+ */
132
135
  heartbeatIntervalMs: number;
136
+ /**
137
+ * Number of elapsed heartbeats without a response message before we consider
138
+ * the connection dead.
139
+ */
133
140
  heartbeatsUntilDead: number;
141
+ /**
142
+ * Duration to wait between connection disconnect and actual session disconnect
143
+ */
134
144
  sessionDisconnectGraceMs: number;
145
+ /**
146
+ * The codec to use for encoding/decoding messages over the wire
147
+ */
135
148
  codec: Codec;
136
149
  }
137
150
  /**
@@ -236,6 +249,58 @@ declare class EventDispatcher<T extends EventTypes> {
236
249
  dispatchEvent<K extends T>(eventType: K, event: EventMap[K]): void;
237
250
  }
238
251
 
252
+ /**
253
+ * Options to control the backoff and retry behavior of the client transport's connection behaviour.
254
+ *
255
+ * River implements exponential backoff with jitter to prevent flooding the server
256
+ * when there's an issue with connection establishment.
257
+ *
258
+ * The backoff is calculated via the following:
259
+ * backOff = min(jitter + {@link baseIntervalMs} * 2 ^ budget_consumed, {@link maxBackoffMs})
260
+ *
261
+ * We use a leaky bucket rate limit with a budget of {@link attemptBudgetCapacity} reconnection attempts.
262
+ * Budget only starts to restore after a successful handshake at a rate of one budget per {@link budgetRestoreIntervalMs}.
263
+ */
264
+ interface ConnectionRetryOptions {
265
+ /**
266
+ * The base interval to wait before retrying a connection.
267
+ */
268
+ baseIntervalMs: number;
269
+ /**
270
+ * The maximum random jitter to add to the total backoff time.
271
+ */
272
+ maxJitterMs: number;
273
+ /**
274
+ * The maximum amount of time to wait before retrying a connection.
275
+ * This does not include the jitter.
276
+ */
277
+ maxBackoffMs: number;
278
+ /**
279
+ * The max number of times to attempt a connection before a successful handshake.
280
+ * This persists across connections but starts restoring budget after a successful handshake.
281
+ * The restoration interval depends on {@link budgetRestoreIntervalMs}
282
+ */
283
+ attemptBudgetCapacity: number;
284
+ /**
285
+ * After a successful connection attempt, how long to wait before we restore a single budget.
286
+ */
287
+ budgetRestoreIntervalMs: number;
288
+ }
289
+ declare class LeakyBucketRateLimit {
290
+ private budgetConsumed;
291
+ private intervalHandles;
292
+ private readonly options;
293
+ constructor(options: ConnectionRetryOptions);
294
+ getBackoffMs(user: TransportClientId): number;
295
+ get totalBudgetRestoreTime(): number;
296
+ consumeBudget(user: TransportClientId): void;
297
+ getBudgetConsumed(user: TransportClientId): number;
298
+ hasBudget(user: TransportClientId): boolean;
299
+ startRestoringBudget(user: TransportClientId): void;
300
+ private stopLeak;
301
+ close(): void;
302
+ }
303
+
239
304
  /**
240
305
  * Represents the possible states of a transport.
241
306
  * @property {'open'} open - The transport is open and operational (note that this doesn't mean it is actively connected)
@@ -243,11 +308,14 @@ declare class EventDispatcher<T extends EventTypes> {
243
308
  * @property {'destroyed'} destroyed - The transport is permanently destroyed and cannot be reopened.
244
309
  */
245
310
  type TransportStatus = 'open' | 'closed' | 'destroyed';
246
- type TransportOptions = {
247
- retryIntervalMs: number;
248
- retryJitterMs: number;
249
- retryAttemptsMax: number;
250
- } & SessionOptions;
311
+ type ProvidedTransportOptions = Partial<SessionOptions>;
312
+ type TransportOptions = Required<ProvidedTransportOptions>;
313
+ interface ProvidedClientTransportOptions extends ProvidedTransportOptions {
314
+ connectionRetryOptions?: Partial<ConnectionRetryOptions>;
315
+ }
316
+ interface ClientTransportOptions extends Required<ProvidedClientTransportOptions> {
317
+ connectionRetryOptions: ConnectionRetryOptions;
318
+ }
251
319
  /**
252
320
  * Transports manage the lifecycle (creation/deletion) of sessions and connections. Its responsibilities include:
253
321
  *
@@ -321,7 +389,7 @@ declare abstract class Transport<ConnType extends Connection> {
321
389
  * @param codec The codec used to encode and decode messages.
322
390
  * @param clientId The client ID of this transport.
323
391
  */
324
- constructor(clientId: TransportClientId, providedOptions?: Partial<TransportOptions>);
392
+ constructor(clientId: TransportClientId, providedOptions?: ProvidedTransportOptions);
325
393
  /**
326
394
  * This is called immediately after a new connection is established and we
327
395
  * may or may not know the identity of the connected client.
@@ -392,12 +460,17 @@ declare abstract class Transport<ConnType extends Connection> {
392
460
  destroy(): void;
393
461
  }
394
462
  declare abstract class ClientTransport<ConnType extends Connection> extends Transport<ConnType> {
463
+ /**
464
+ * The options for this transport.
465
+ */
466
+ protected options: ClientTransportOptions;
395
467
  /**
396
468
  * The map of reconnect promises for each client ID.
397
469
  */
398
470
  inflightConnectionPromises: Map<TransportClientId, Promise<ConnType>>;
471
+ retryBudget: LeakyBucketRateLimit;
399
472
  tryReconnecting: boolean;
400
- constructor(clientId: TransportClientId, providedOptions?: Partial<TransportOptions>);
473
+ constructor(clientId: TransportClientId, providedOptions?: ProvidedTransportOptions);
401
474
  protected handleConnection(conn: ConnType, to: TransportClientId): void;
402
475
  receiveHandshakeResponseMessage(data: Uint8Array): false | {
403
476
  instanceId: string;
@@ -416,12 +489,12 @@ declare abstract class ClientTransport<ConnType extends Connection> extends Tran
416
489
  * Manually attempts to connect to a client.
417
490
  * @param to The client ID of the node to connect to.
418
491
  */
419
- connect(to: TransportClientId, attempt?: number): Promise<void>;
492
+ connect(to: TransportClientId): Promise<void>;
420
493
  protected sendHandshake(to: TransportClientId, conn: ConnType): void;
421
- protected onDisconnect(conn: ConnType, session: Session<ConnType>): void;
494
+ close(): void;
422
495
  }
423
496
  declare abstract class ServerTransport<ConnType extends Connection> extends Transport<ConnType> {
424
- constructor(clientId: TransportClientId, providedOptions?: Partial<TransportOptions>);
497
+ constructor(clientId: TransportClientId, providedOptions?: Omit<Partial<ProvidedTransportOptions>, 'connectionRetryOptions'>);
425
498
  protected handleConnection(conn: ConnType): void;
426
499
  receiveHandshakeRequestMessage(data: Uint8Array, conn: ConnType): false | {
427
500
  instanceId: string;
@@ -429,4 +502,4 @@ declare abstract class ServerTransport<ConnType extends Connection> extends Tran
429
502
  };
430
503
  }
431
504
 
432
- export { Connection as C, EventMap as E, OpaqueTransportMessage as O, PartialTransportMessage as P, ServerTransport as S, Transport as T, ClientTransport as a, TransportClientId as b, TransportOptions as c, Session as d, TransportStatus as e, TransportMessageSchema as f, OpaqueTransportMessageSchema as g, TransportMessage as h, isStreamOpen as i, isStreamClose as j, EventTypes as k, EventHandler as l, ProtocolError as m, ProtocolErrorType as n };
505
+ export { Connection as C, EventMap as E, OpaqueTransportMessage as O, PartialTransportMessage as P, ServerTransport as S, Transport as T, ClientTransport as a, TransportClientId as b, ProvidedClientTransportOptions as c, ProvidedTransportOptions as d, Session as e, TransportStatus as f, TransportMessageSchema as g, OpaqueTransportMessageSchema as h, TransportMessage as i, isStreamOpen as j, isStreamClose as k, EventTypes as l, EventHandler as m, ProtocolError as n, ProtocolErrorType as o };
@@ -1,6 +1,6 @@
1
- import { A as AnyService, P as PayloadType, b as Result, R as RiverError, S as ServiceContext, d as ProcType, e as ProcInput, f as ProcOutput, g as ProcErrors, h as ProcHasInit, i as ProcInit } from '../builder-660d3140.js';
2
- export { E as Err, O as Ok, m as ProcHandler, k as ProcListing, a as Procedure, p as RiverErrorSchema, c as RiverUncaughtSchema, l as Service, j as ServiceBuilder, n as ServiceContextWithState, o as ServiceContextWithTransportInfo, U as UNCAUGHT_ERROR, V as ValidProcType, s as serializeService } from '../builder-660d3140.js';
3
- import { S as ServerTransport, C as Connection, a as ClientTransport, b as TransportClientId } from '../index-76b801f8.js';
1
+ import { A as AnyService, P as PayloadType, b as Result, R as RiverError, S as ServiceContext, d as ProcType, e as ProcInput, f as ProcOutput, g as ProcErrors, h as ProcHasInit, i as ProcInit } from '../builder-ebd945c0.js';
2
+ export { E as Err, O as Ok, m as ProcHandler, k as ProcListing, a as Procedure, p as RiverErrorSchema, c as RiverUncaughtSchema, l as Service, j as ServiceBuilder, n as ServiceContextWithState, o as ServiceContextWithTransportInfo, U as UNCAUGHT_ERROR, V as ValidProcType, s as serializeService } from '../builder-ebd945c0.js';
3
+ import { S as ServerTransport, C as Connection, a as ClientTransport, b as TransportClientId } from '../index-bbccacef.js';
4
4
  import { Pushable } from 'it-pushable';
5
5
  import { Static } from '@sinclair/typebox';
6
6
  import '../types-3e5768ec.js';
@@ -1,6 +1,6 @@
1
- import { A as AnyService, P as PayloadType, b as Result, R as RiverError, S as ServiceContext, d as ProcType, e as ProcInput, f as ProcOutput, g as ProcErrors, h as ProcHasInit, i as ProcInit } from '../builder-660d3140.js';
2
- export { E as Err, O as Ok, m as ProcHandler, k as ProcListing, a as Procedure, p as RiverErrorSchema, c as RiverUncaughtSchema, l as Service, j as ServiceBuilder, n as ServiceContextWithState, o as ServiceContextWithTransportInfo, U as UNCAUGHT_ERROR, V as ValidProcType, s as serializeService } from '../builder-660d3140.js';
3
- import { S as ServerTransport, C as Connection, a as ClientTransport, b as TransportClientId } from '../index-76b801f8.js';
1
+ import { A as AnyService, P as PayloadType, b as Result, R as RiverError, S as ServiceContext, d as ProcType, e as ProcInput, f as ProcOutput, g as ProcErrors, h as ProcHasInit, i as ProcInit } from '../builder-ebd945c0.js';
2
+ export { E as Err, O as Ok, m as ProcHandler, k as ProcListing, a as Procedure, p as RiverErrorSchema, c as RiverUncaughtSchema, l as Service, j as ServiceBuilder, n as ServiceContextWithState, o as ServiceContextWithTransportInfo, U as UNCAUGHT_ERROR, V as ValidProcType, s as serializeService } from '../builder-ebd945c0.js';
3
+ import { S as ServerTransport, C as Connection, a as ClientTransport, b as TransportClientId } from '../index-bbccacef.js';
4
4
  import { Pushable } from 'it-pushable';
5
5
  import { Static } from '@sinclair/typebox';
6
6
  import '../types-3e5768ec.js';