@replit/river 0.10.10 → 0.10.12
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/README.md +19 -0
- package/dist/{chunk-FTVAZZ6B.js → chunk-5IC5XMWK.js} +33 -20
- package/dist/transport/impls/ws/client.cjs +33 -20
- package/dist/transport/impls/ws/client.d.cts +5 -3
- package/dist/transport/impls/ws/client.d.ts +5 -3
- package/dist/transport/impls/ws/client.js +1 -1
- package/dist/transport/index.d.cts +4 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/util/testHelpers.cjs +33 -20
- package/dist/util/testHelpers.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ In another file for the client (to create a separate entrypoint),
|
|
|
89
89
|
import WebSocket from 'isomorphic-ws';
|
|
90
90
|
import { WebSocketClientTransport } from '@replit/river/transport/ws/client';
|
|
91
91
|
import { createClient } from '@replit/river';
|
|
92
|
+
import type ServiceSurface from './server';
|
|
92
93
|
|
|
93
94
|
const websocketUrl = `ws://localhost:3000`;
|
|
94
95
|
const transport = new WebSocketClientTransport(
|
|
@@ -118,6 +119,24 @@ bindLogger(console.log);
|
|
|
118
119
|
setLevel('info');
|
|
119
120
|
```
|
|
120
121
|
|
|
122
|
+
To listen for connection status changes,
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
transport.addEventListener('connectionStatus', (evt) => {
|
|
126
|
+
if (evt.status === 'connect') {
|
|
127
|
+
// do something
|
|
128
|
+
} else if (evt.status === 'disconnect') {
|
|
129
|
+
// do something else
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> [!note] WebSocket connection behaviour
|
|
135
|
+
> WebSocket is an idle protocol. This means that when the underlying connection drops, the WebSocket
|
|
136
|
+
> may still think it is still connected (e.g. turning off the network via devtools).
|
|
137
|
+
> You can use `window.addEventListener('online', ...);` and `window.addEventListener('offline', ...);` to
|
|
138
|
+
> know if you need to recreate the transport.
|
|
139
|
+
|
|
121
140
|
### Further examples
|
|
122
141
|
|
|
123
142
|
We've also provided an end-to-end testing environment using Next.js, and a simple backend connected
|
|
@@ -27,20 +27,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
27
27
|
reconnectPromises;
|
|
28
28
|
tryReconnecting = true;
|
|
29
29
|
/**
|
|
30
|
-
* Creates a new
|
|
30
|
+
* Creates a new WebSocketClientTransport instance.
|
|
31
31
|
* @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
|
|
32
|
-
* @param
|
|
32
|
+
* @param sessionId The ID of the client using the transport. This should be unique per session.
|
|
33
|
+
* @param serverId The ID of the server this transport is connecting to.
|
|
33
34
|
* @param providedOptions An optional object containing configuration options for the transport.
|
|
34
35
|
*/
|
|
35
|
-
constructor(wsGetter,
|
|
36
|
+
constructor(wsGetter, sessionId, serverId, providedOptions) {
|
|
36
37
|
const options = { ...defaultOptions, ...providedOptions };
|
|
37
|
-
super(options.codec,
|
|
38
|
+
super(options.codec, sessionId);
|
|
38
39
|
this.wsGetter = wsGetter;
|
|
39
40
|
this.serverId = serverId;
|
|
40
41
|
this.options = options;
|
|
41
42
|
this.reconnectPromises = /* @__PURE__ */ new Map();
|
|
42
43
|
this.createNewConnection(this.serverId);
|
|
43
44
|
}
|
|
45
|
+
reopen() {
|
|
46
|
+
if (this.state === "destroyed") {
|
|
47
|
+
throw new Error("cant reopen a destroyed connection");
|
|
48
|
+
}
|
|
49
|
+
this.state = "open";
|
|
50
|
+
this.createNewConnection(this.serverId);
|
|
51
|
+
}
|
|
44
52
|
async createNewConnection(to, attempt = 0) {
|
|
45
53
|
if (this.state === "destroyed") {
|
|
46
54
|
throw new Error("cant reopen a destroyed connection");
|
|
@@ -55,23 +63,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
55
63
|
}
|
|
56
64
|
reconnectPromise = new Promise(async (resolve) => {
|
|
57
65
|
log?.info(`${this.clientId} -- establishing a new websocket to ${to}`);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
try {
|
|
67
|
+
const ws = await this.wsGetter(to);
|
|
68
|
+
if (ws.readyState === ws.OPEN) {
|
|
69
|
+
return resolve({ ws });
|
|
70
|
+
}
|
|
71
|
+
if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
|
|
72
|
+
return resolve({ err: "ws is closing or closed" });
|
|
73
|
+
}
|
|
74
|
+
const onOpen = () => {
|
|
75
|
+
ws.removeEventListener("open", onOpen);
|
|
76
|
+
resolve({ ws });
|
|
77
|
+
};
|
|
78
|
+
const onClose = (evt) => {
|
|
79
|
+
ws.removeEventListener("close", onClose);
|
|
80
|
+
resolve({ err: evt.reason });
|
|
81
|
+
};
|
|
82
|
+
ws.addEventListener("open", onOpen);
|
|
83
|
+
ws.addEventListener("close", onClose);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
const reason = e instanceof Error ? e.message : "unknown reason";
|
|
86
|
+
return resolve({ err: `couldn't get a new websocket: ${reason}` });
|
|
64
87
|
}
|
|
65
|
-
const onOpen = () => {
|
|
66
|
-
ws.removeEventListener("open", onOpen);
|
|
67
|
-
resolve({ ws });
|
|
68
|
-
};
|
|
69
|
-
const onClose = (evt) => {
|
|
70
|
-
ws.removeEventListener("close", onClose);
|
|
71
|
-
resolve({ err: evt.reason });
|
|
72
|
-
};
|
|
73
|
-
ws.addEventListener("open", onOpen);
|
|
74
|
-
ws.addEventListener("close", onClose);
|
|
75
88
|
});
|
|
76
89
|
this.reconnectPromises.set(to, reconnectPromise);
|
|
77
90
|
}
|
|
@@ -417,20 +417,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
417
417
|
reconnectPromises;
|
|
418
418
|
tryReconnecting = true;
|
|
419
419
|
/**
|
|
420
|
-
* Creates a new
|
|
420
|
+
* Creates a new WebSocketClientTransport instance.
|
|
421
421
|
* @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
|
|
422
|
-
* @param
|
|
422
|
+
* @param sessionId The ID of the client using the transport. This should be unique per session.
|
|
423
|
+
* @param serverId The ID of the server this transport is connecting to.
|
|
423
424
|
* @param providedOptions An optional object containing configuration options for the transport.
|
|
424
425
|
*/
|
|
425
|
-
constructor(wsGetter,
|
|
426
|
+
constructor(wsGetter, sessionId, serverId, providedOptions) {
|
|
426
427
|
const options = { ...defaultOptions, ...providedOptions };
|
|
427
|
-
super(options.codec,
|
|
428
|
+
super(options.codec, sessionId);
|
|
428
429
|
this.wsGetter = wsGetter;
|
|
429
430
|
this.serverId = serverId;
|
|
430
431
|
this.options = options;
|
|
431
432
|
this.reconnectPromises = /* @__PURE__ */ new Map();
|
|
432
433
|
this.createNewConnection(this.serverId);
|
|
433
434
|
}
|
|
435
|
+
reopen() {
|
|
436
|
+
if (this.state === "destroyed") {
|
|
437
|
+
throw new Error("cant reopen a destroyed connection");
|
|
438
|
+
}
|
|
439
|
+
this.state = "open";
|
|
440
|
+
this.createNewConnection(this.serverId);
|
|
441
|
+
}
|
|
434
442
|
async createNewConnection(to, attempt = 0) {
|
|
435
443
|
if (this.state === "destroyed") {
|
|
436
444
|
throw new Error("cant reopen a destroyed connection");
|
|
@@ -445,23 +453,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
445
453
|
}
|
|
446
454
|
reconnectPromise = new Promise(async (resolve) => {
|
|
447
455
|
log?.info(`${this.clientId} -- establishing a new websocket to ${to}`);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
456
|
+
try {
|
|
457
|
+
const ws = await this.wsGetter(to);
|
|
458
|
+
if (ws.readyState === ws.OPEN) {
|
|
459
|
+
return resolve({ ws });
|
|
460
|
+
}
|
|
461
|
+
if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
|
|
462
|
+
return resolve({ err: "ws is closing or closed" });
|
|
463
|
+
}
|
|
464
|
+
const onOpen = () => {
|
|
465
|
+
ws.removeEventListener("open", onOpen);
|
|
466
|
+
resolve({ ws });
|
|
467
|
+
};
|
|
468
|
+
const onClose = (evt) => {
|
|
469
|
+
ws.removeEventListener("close", onClose);
|
|
470
|
+
resolve({ err: evt.reason });
|
|
471
|
+
};
|
|
472
|
+
ws.addEventListener("open", onOpen);
|
|
473
|
+
ws.addEventListener("close", onClose);
|
|
474
|
+
} catch (e) {
|
|
475
|
+
const reason = e instanceof Error ? e.message : "unknown reason";
|
|
476
|
+
return resolve({ err: `couldn't get a new websocket: ${reason}` });
|
|
454
477
|
}
|
|
455
|
-
const onOpen = () => {
|
|
456
|
-
ws.removeEventListener("open", onOpen);
|
|
457
|
-
resolve({ ws });
|
|
458
|
-
};
|
|
459
|
-
const onClose = (evt) => {
|
|
460
|
-
ws.removeEventListener("close", onClose);
|
|
461
|
-
resolve({ err: evt.reason });
|
|
462
|
-
};
|
|
463
|
-
ws.addEventListener("open", onOpen);
|
|
464
|
-
ws.addEventListener("close", onClose);
|
|
465
478
|
});
|
|
466
479
|
this.reconnectPromises.set(to, reconnectPromise);
|
|
467
480
|
}
|
|
@@ -29,12 +29,14 @@ declare class WebSocketClientTransport extends Transport<WebSocketConnection> {
|
|
|
29
29
|
reconnectPromises: Map<TransportClientId, Promise<WebSocketResult>>;
|
|
30
30
|
tryReconnecting: boolean;
|
|
31
31
|
/**
|
|
32
|
-
* Creates a new
|
|
32
|
+
* Creates a new WebSocketClientTransport instance.
|
|
33
33
|
* @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
|
|
34
|
-
* @param
|
|
34
|
+
* @param sessionId The ID of the client using the transport. This should be unique per session.
|
|
35
|
+
* @param serverId The ID of the server this transport is connecting to.
|
|
35
36
|
* @param providedOptions An optional object containing configuration options for the transport.
|
|
36
37
|
*/
|
|
37
|
-
constructor(wsGetter: () => Promise<WebSocket>,
|
|
38
|
+
constructor(wsGetter: () => Promise<WebSocket>, sessionId: TransportClientId, serverId: TransportClientId, providedOptions?: Partial<Options>);
|
|
39
|
+
reopen(): void;
|
|
38
40
|
createNewConnection(to: string, attempt?: number): Promise<void>;
|
|
39
41
|
close(): Promise<void>;
|
|
40
42
|
destroy(): Promise<void>;
|
|
@@ -29,12 +29,14 @@ declare class WebSocketClientTransport extends Transport<WebSocketConnection> {
|
|
|
29
29
|
reconnectPromises: Map<TransportClientId, Promise<WebSocketResult>>;
|
|
30
30
|
tryReconnecting: boolean;
|
|
31
31
|
/**
|
|
32
|
-
* Creates a new
|
|
32
|
+
* Creates a new WebSocketClientTransport instance.
|
|
33
33
|
* @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
|
|
34
|
-
* @param
|
|
34
|
+
* @param sessionId The ID of the client using the transport. This should be unique per session.
|
|
35
|
+
* @param serverId The ID of the server this transport is connecting to.
|
|
35
36
|
* @param providedOptions An optional object containing configuration options for the transport.
|
|
36
37
|
*/
|
|
37
|
-
constructor(wsGetter: () => Promise<WebSocket>,
|
|
38
|
+
constructor(wsGetter: () => Promise<WebSocket>, sessionId: TransportClientId, serverId: TransportClientId, providedOptions?: Partial<Options>);
|
|
39
|
+
reopen(): void;
|
|
38
40
|
createNewConnection(to: string, attempt?: number): Promise<void>;
|
|
39
41
|
close(): Promise<void>;
|
|
40
42
|
destroy(): Promise<void>;
|
|
@@ -205,6 +205,10 @@ declare abstract class Transport<ConnType extends Connection> {
|
|
|
205
205
|
* The downstream implementation needs to implement this. If the downstream
|
|
206
206
|
* transport cannot make new outgoing connections (e.g. a server transport),
|
|
207
207
|
* it is ok to log an error and return.
|
|
208
|
+
*
|
|
209
|
+
* Consumers of river should never need to call this directly.
|
|
210
|
+
* Instead, look for a `reopen` method on the transport.
|
|
211
|
+
*
|
|
208
212
|
* @param to The client ID of the node to connect to.
|
|
209
213
|
* @returns The new connection object.
|
|
210
214
|
*/
|
|
@@ -205,6 +205,10 @@ declare abstract class Transport<ConnType extends Connection> {
|
|
|
205
205
|
* The downstream implementation needs to implement this. If the downstream
|
|
206
206
|
* transport cannot make new outgoing connections (e.g. a server transport),
|
|
207
207
|
* it is ok to log an error and return.
|
|
208
|
+
*
|
|
209
|
+
* Consumers of river should never need to call this directly.
|
|
210
|
+
* Instead, look for a `reopen` method on the transport.
|
|
211
|
+
*
|
|
208
212
|
* @param to The client ID of the node to connect to.
|
|
209
213
|
* @returns The new connection object.
|
|
210
214
|
*/
|
|
@@ -454,20 +454,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
454
454
|
reconnectPromises;
|
|
455
455
|
tryReconnecting = true;
|
|
456
456
|
/**
|
|
457
|
-
* Creates a new
|
|
457
|
+
* Creates a new WebSocketClientTransport instance.
|
|
458
458
|
* @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
|
|
459
|
-
* @param
|
|
459
|
+
* @param sessionId The ID of the client using the transport. This should be unique per session.
|
|
460
|
+
* @param serverId The ID of the server this transport is connecting to.
|
|
460
461
|
* @param providedOptions An optional object containing configuration options for the transport.
|
|
461
462
|
*/
|
|
462
|
-
constructor(wsGetter,
|
|
463
|
+
constructor(wsGetter, sessionId, serverId, providedOptions) {
|
|
463
464
|
const options = { ...defaultOptions, ...providedOptions };
|
|
464
|
-
super(options.codec,
|
|
465
|
+
super(options.codec, sessionId);
|
|
465
466
|
this.wsGetter = wsGetter;
|
|
466
467
|
this.serverId = serverId;
|
|
467
468
|
this.options = options;
|
|
468
469
|
this.reconnectPromises = /* @__PURE__ */ new Map();
|
|
469
470
|
this.createNewConnection(this.serverId);
|
|
470
471
|
}
|
|
472
|
+
reopen() {
|
|
473
|
+
if (this.state === "destroyed") {
|
|
474
|
+
throw new Error("cant reopen a destroyed connection");
|
|
475
|
+
}
|
|
476
|
+
this.state = "open";
|
|
477
|
+
this.createNewConnection(this.serverId);
|
|
478
|
+
}
|
|
471
479
|
async createNewConnection(to, attempt = 0) {
|
|
472
480
|
if (this.state === "destroyed") {
|
|
473
481
|
throw new Error("cant reopen a destroyed connection");
|
|
@@ -482,23 +490,28 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
482
490
|
}
|
|
483
491
|
reconnectPromise = new Promise(async (resolve) => {
|
|
484
492
|
log?.info(`${this.clientId} -- establishing a new websocket to ${to}`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
493
|
+
try {
|
|
494
|
+
const ws = await this.wsGetter(to);
|
|
495
|
+
if (ws.readyState === ws.OPEN) {
|
|
496
|
+
return resolve({ ws });
|
|
497
|
+
}
|
|
498
|
+
if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
|
|
499
|
+
return resolve({ err: "ws is closing or closed" });
|
|
500
|
+
}
|
|
501
|
+
const onOpen = () => {
|
|
502
|
+
ws.removeEventListener("open", onOpen);
|
|
503
|
+
resolve({ ws });
|
|
504
|
+
};
|
|
505
|
+
const onClose = (evt) => {
|
|
506
|
+
ws.removeEventListener("close", onClose);
|
|
507
|
+
resolve({ err: evt.reason });
|
|
508
|
+
};
|
|
509
|
+
ws.addEventListener("open", onOpen);
|
|
510
|
+
ws.addEventListener("close", onClose);
|
|
511
|
+
} catch (e) {
|
|
512
|
+
const reason = e instanceof Error ? e.message : "unknown reason";
|
|
513
|
+
return resolve({ err: `couldn't get a new websocket: ${reason}` });
|
|
491
514
|
}
|
|
492
|
-
const onOpen = () => {
|
|
493
|
-
ws.removeEventListener("open", onOpen);
|
|
494
|
-
resolve({ ws });
|
|
495
|
-
};
|
|
496
|
-
const onClose = (evt) => {
|
|
497
|
-
ws.removeEventListener("close", onClose);
|
|
498
|
-
resolve({ err: evt.reason });
|
|
499
|
-
};
|
|
500
|
-
ws.addEventListener("open", onOpen);
|
|
501
|
-
ws.addEventListener("close", onClose);
|
|
502
515
|
});
|
|
503
516
|
this.reconnectPromises.set(to, reconnectPromise);
|
|
504
517
|
}
|
package/dist/util/testHelpers.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replit/river",
|
|
3
3
|
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.12",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/ws": "^8.5.5",
|
|
66
|
-
"@vitest/ui": "^1.1
|
|
66
|
+
"@vitest/ui": "^1.2.1",
|
|
67
67
|
"prettier": "^3.0.0",
|
|
68
68
|
"tsup": "^7.2.0",
|
|
69
69
|
"typescript": "^5.2.2",
|
|
70
|
-
"vitest": "^1.1
|
|
70
|
+
"vitest": "^1.2.1"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"check": "tsc --noEmit && npx prettier . --check",
|