@replit/river 0.1.10 → 0.2.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/__tests__/bandwidth.bench.d.ts +1 -0
- package/dist/__tests__/bandwidth.bench.js +82 -0
- package/dist/__tests__/integration.test.d.ts +5 -4
- package/dist/__tests__/integration.test.js +70 -30
- package/dist/__tests__/largePayload.json +33 -0
- package/dist/__tests__/typescript-stress.test.d.ts +740 -0
- package/dist/__tests__/typescript-stress.test.js +10 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/router/client.js +9 -9
- package/dist/transport/message.d.ts +1 -0
- package/dist/transport/message.js +2 -0
- package/dist/transport/types.js +4 -0
- package/dist/transport/util.d.ts +4 -4
- package/dist/transport/util.js +26 -21
- package/dist/transport/ws.d.ts +19 -4
- package/dist/transport/ws.js +75 -10
- package/dist/transport/ws.test.js +63 -29
- package/package.json +3 -2
|
@@ -6,17 +6,19 @@ import { createServer } from '../router/server';
|
|
|
6
6
|
import { Transport } from '../transport/types';
|
|
7
7
|
import { NaiveJsonCodec } from '../codec/json';
|
|
8
8
|
import { createClient } from '../router/client';
|
|
9
|
+
const input = Type.Object({ a: Type.Number() });
|
|
10
|
+
const output = Type.Object({ b: Type.Number() });
|
|
9
11
|
const fnBody = {
|
|
10
12
|
type: 'rpc',
|
|
11
|
-
input
|
|
12
|
-
output
|
|
13
|
+
input,
|
|
14
|
+
output,
|
|
13
15
|
async handler(_state, msg) {
|
|
14
16
|
return reply(msg, { b: msg.payload.a });
|
|
15
17
|
},
|
|
16
18
|
};
|
|
17
19
|
// typescript is limited to max 50 constraints
|
|
18
20
|
// see: https://github.com/microsoft/TypeScript/issues/33541
|
|
19
|
-
const
|
|
21
|
+
export const StupidlyLargeService = () => ServiceBuilder.create('test')
|
|
20
22
|
.defineProcedure('f1', fnBody)
|
|
21
23
|
.defineProcedure('f2', fnBody)
|
|
22
24
|
.defineProcedure('f3', fnBody)
|
|
@@ -80,14 +82,14 @@ export class MockTransport extends Transport {
|
|
|
80
82
|
}
|
|
81
83
|
describe("ensure typescript doesn't give up trying to infer the types for large services", () => {
|
|
82
84
|
test('service with many procedures hits typescript limit', () => {
|
|
83
|
-
expect(serializeService(
|
|
85
|
+
expect(serializeService(StupidlyLargeService())).toBeTruthy();
|
|
84
86
|
});
|
|
85
87
|
test('serverclient should support many services with many procedures', async () => {
|
|
86
88
|
const listing = {
|
|
87
|
-
a:
|
|
88
|
-
b:
|
|
89
|
-
c:
|
|
90
|
-
d:
|
|
89
|
+
a: StupidlyLargeService(),
|
|
90
|
+
b: StupidlyLargeService(),
|
|
91
|
+
c: StupidlyLargeService(),
|
|
92
|
+
d: StupidlyLargeService(),
|
|
91
93
|
};
|
|
92
94
|
const server = await createServer(new MockTransport('SERVER'), listing);
|
|
93
95
|
const client = createClient(new MockTransport('client'));
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export { TransportMessageSchema, OpaqueTransportMessageSchema, TransportAckSchem
|
|
|
11
11
|
export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, TransportMessageAck, } from './transport/message';
|
|
12
12
|
export { StreamTransport } from './transport/stream';
|
|
13
13
|
export { WebSocketTransport } from './transport/ws';
|
|
14
|
-
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage,
|
|
14
|
+
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, createLocalWebSocketClient, } from './transport/util';
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,4 @@ export { Transport } from './transport/types';
|
|
|
6
6
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, TransportAckSchema, msg, payloadToTransportMessage, ack, reply, } from './transport/message';
|
|
7
7
|
export { StreamTransport } from './transport/stream';
|
|
8
8
|
export { WebSocketTransport } from './transport/ws';
|
|
9
|
-
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage,
|
|
9
|
+
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, createLocalWebSocketClient, } from './transport/util';
|
package/dist/router/client.js
CHANGED
|
@@ -23,28 +23,28 @@ export const createClient = (transport) => _createRecursiveProxy(async (opts) =>
|
|
|
23
23
|
const [input] = opts.args;
|
|
24
24
|
if (input === undefined) {
|
|
25
25
|
// stream case
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
//
|
|
26
|
+
const inputStream = pushable({ objectMode: true });
|
|
27
|
+
const outputStream = pushable({ objectMode: true });
|
|
28
|
+
// input -> transport
|
|
29
29
|
// this gets cleaned up on i.end() which is called by closeHandler
|
|
30
30
|
(async () => {
|
|
31
|
-
for await (const rawIn of
|
|
31
|
+
for await (const rawIn of inputStream) {
|
|
32
32
|
transport.send(msg(transport.clientId, 'SERVER', serviceName, procName, rawIn));
|
|
33
33
|
}
|
|
34
34
|
})();
|
|
35
|
-
// transport ->
|
|
35
|
+
// transport -> output
|
|
36
36
|
const listener = (msg) => {
|
|
37
37
|
if (msg.serviceName === serviceName && msg.procedureName === procName) {
|
|
38
|
-
|
|
38
|
+
outputStream.push(msg.payload);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
transport.addMessageListener(listener);
|
|
42
42
|
const closeHandler = () => {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
inputStream.end();
|
|
44
|
+
outputStream.end();
|
|
45
45
|
transport.removeMessageListener(listener);
|
|
46
46
|
};
|
|
47
|
-
return [
|
|
47
|
+
return [inputStream, outputStream, closeHandler];
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
50
|
// rpc case
|
|
@@ -30,6 +30,7 @@ export type MessageId = string;
|
|
|
30
30
|
export type OpaqueTransportMessage = TransportMessage<unknown>;
|
|
31
31
|
export type TransportClientId = 'SERVER' | string;
|
|
32
32
|
export declare const TransportAckSchema: import("@sinclair/typebox").TObject<{
|
|
33
|
+
id: import("@sinclair/typebox").TString;
|
|
33
34
|
from: import("@sinclair/typebox").TString;
|
|
34
35
|
ack: import("@sinclair/typebox").TString;
|
|
35
36
|
}>;
|
|
@@ -12,6 +12,7 @@ export const TransportMessageSchema = (t) => Type.Object({
|
|
|
12
12
|
});
|
|
13
13
|
export const OpaqueTransportMessageSchema = TransportMessageSchema(Type.Unknown());
|
|
14
14
|
export const TransportAckSchema = Type.Object({
|
|
15
|
+
id: Type.String(),
|
|
15
16
|
from: Type.String(),
|
|
16
17
|
ack: Type.String(),
|
|
17
18
|
});
|
|
@@ -30,6 +31,7 @@ export function payloadToTransportMessage(payload) {
|
|
|
30
31
|
}
|
|
31
32
|
export function ack(msg) {
|
|
32
33
|
return {
|
|
34
|
+
id: nanoid(),
|
|
33
35
|
from: msg.to,
|
|
34
36
|
ack: msg.id,
|
|
35
37
|
};
|
package/dist/transport/types.js
CHANGED
|
@@ -12,6 +12,7 @@ export class Transport {
|
|
|
12
12
|
this.clientId = clientId;
|
|
13
13
|
}
|
|
14
14
|
onMessage(msg) {
|
|
15
|
+
// TODO: try catch from string buf
|
|
15
16
|
const parsedMsg = this.codec.fromStringBuf(msg.toString());
|
|
16
17
|
if (Value.Check(TransportAckSchema, parsedMsg)) {
|
|
17
18
|
// process ack
|
|
@@ -30,6 +31,9 @@ export class Transport {
|
|
|
30
31
|
}
|
|
31
32
|
this.send(ack(parsedMsg));
|
|
32
33
|
}
|
|
34
|
+
else {
|
|
35
|
+
// TODO: warn on malformed
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
addMessageListener(handler) {
|
|
35
39
|
this.handlers.add(handler);
|
package/dist/transport/util.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ import http from 'http';
|
|
|
3
3
|
import WebSocket from 'isomorphic-ws';
|
|
4
4
|
import { WebSocketServer } from 'ws';
|
|
5
5
|
import { Transport } from './types';
|
|
6
|
+
import { WebSocketTransport } from './ws';
|
|
6
7
|
import { OpaqueTransportMessage } from './message';
|
|
7
8
|
export declare function createWebSocketServer(server: http.Server): Promise<WebSocket.Server<typeof WebSocket, typeof http.IncomingMessage>>;
|
|
8
|
-
export declare function onServerReady(server: http.Server
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function createWebSocketClient(port: number): Promise<WebSocket>;
|
|
9
|
+
export declare function onServerReady(server: http.Server): Promise<number>;
|
|
10
|
+
export declare function createLocalWebSocketClient(port: number): Promise<WebSocket>;
|
|
11
|
+
export declare function createWsTransports(port: number, wss: WebSocketServer): [WebSocketTransport, WebSocketTransport];
|
|
12
12
|
export declare function waitForMessage(t: Transport, filter?: (msg: OpaqueTransportMessage) => boolean): Promise<unknown>;
|
package/dist/transport/util.js
CHANGED
|
@@ -4,31 +4,36 @@ import { WebSocketTransport } from './ws';
|
|
|
4
4
|
export async function createWebSocketServer(server) {
|
|
5
5
|
return new WebSocketServer({ server });
|
|
6
6
|
}
|
|
7
|
-
export async function onServerReady(server
|
|
8
|
-
return new Promise((resolve) => {
|
|
9
|
-
server.listen(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
new WebSocketTransport(await clientSockPromise, 'client'),
|
|
18
|
-
new WebSocketTransport(serverSock, 'SERVER'),
|
|
19
|
-
]);
|
|
7
|
+
export async function onServerReady(server) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
server.listen(() => {
|
|
10
|
+
const addr = server.address();
|
|
11
|
+
if (typeof addr === 'object' && addr) {
|
|
12
|
+
resolve(addr.port);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
reject(new Error("couldn't find a port to allocate"));
|
|
16
|
+
}
|
|
20
17
|
});
|
|
21
18
|
});
|
|
22
19
|
}
|
|
23
|
-
export async function
|
|
24
|
-
return new
|
|
25
|
-
socket.addEventListener('open', () => resolve());
|
|
26
|
-
});
|
|
20
|
+
export async function createLocalWebSocketClient(port) {
|
|
21
|
+
return new WebSocket(`ws://localhost:${port}`);
|
|
27
22
|
}
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
export function createWsTransports(port, wss) {
|
|
24
|
+
return [
|
|
25
|
+
new WebSocketTransport(async () => {
|
|
26
|
+
return createLocalWebSocketClient(port);
|
|
27
|
+
}, 'client'),
|
|
28
|
+
new WebSocketTransport(async () => {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
wss.on('connection', async function onConnect(serverSock) {
|
|
31
|
+
wss.removeListener('connection', onConnect);
|
|
32
|
+
resolve(serverSock);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}, 'SERVER'),
|
|
36
|
+
];
|
|
32
37
|
}
|
|
33
38
|
export async function waitForMessage(t, filter) {
|
|
34
39
|
return new Promise((resolve, _reject) => {
|
package/dist/transport/ws.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/// <reference types="ws" />
|
|
2
|
-
import
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
3
|
import { Transport } from './types';
|
|
4
4
|
import { MessageId, OpaqueTransportMessage, TransportClientId } from './message';
|
|
5
|
-
|
|
5
|
+
interface Options {
|
|
6
|
+
retryIntervalMs: number;
|
|
7
|
+
}
|
|
8
|
+
type WebSocketResult = {
|
|
6
9
|
ws: WebSocket;
|
|
7
|
-
|
|
10
|
+
} | {
|
|
11
|
+
err: string;
|
|
12
|
+
};
|
|
13
|
+
export declare class WebSocketTransport extends Transport {
|
|
14
|
+
wsGetter: () => Promise<WebSocket>;
|
|
15
|
+
ws?: WebSocket;
|
|
16
|
+
destroyed: boolean;
|
|
17
|
+
reconnectPromise?: Promise<WebSocketResult>;
|
|
18
|
+
options: Options;
|
|
19
|
+
sendQueue: Array<MessageId>;
|
|
20
|
+
constructor(wsGetter: () => Promise<WebSocket>, clientId: TransportClientId, options?: Partial<Options>);
|
|
21
|
+
private tryConnect;
|
|
8
22
|
send(msg: OpaqueTransportMessage): MessageId;
|
|
9
|
-
close(): Promise<void>;
|
|
23
|
+
close(): Promise<void | undefined>;
|
|
10
24
|
}
|
|
25
|
+
export {};
|
package/dist/transport/ws.js
CHANGED
|
@@ -1,23 +1,88 @@
|
|
|
1
1
|
import { Transport } from './types';
|
|
2
2
|
import { NaiveJsonCodec } from '../codec/json';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// - how do we handle forceful client disconnects? (i.e. broken connection, offline)
|
|
7
|
-
// - how do we handle forceful service disconnects (i.e. a crash)?
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
retryIntervalMs: 250,
|
|
5
|
+
};
|
|
8
6
|
export class WebSocketTransport extends Transport {
|
|
7
|
+
wsGetter;
|
|
9
8
|
ws;
|
|
10
|
-
|
|
9
|
+
destroyed;
|
|
10
|
+
reconnectPromise;
|
|
11
|
+
options;
|
|
12
|
+
sendQueue;
|
|
13
|
+
constructor(wsGetter, clientId, options) {
|
|
11
14
|
super(NaiveJsonCodec, clientId);
|
|
12
|
-
this.
|
|
13
|
-
|
|
15
|
+
this.destroyed = false;
|
|
16
|
+
this.wsGetter = wsGetter;
|
|
17
|
+
this.options = { ...defaultOptions, ...options };
|
|
18
|
+
this.sendQueue = [];
|
|
19
|
+
this.tryConnect();
|
|
20
|
+
}
|
|
21
|
+
// postcondition: ws is concretely a WebSocket
|
|
22
|
+
async tryConnect() {
|
|
23
|
+
// wait until it's ready or we get an error
|
|
24
|
+
this.reconnectPromise ??= new Promise(async (resolve) => {
|
|
25
|
+
const ws = await this.wsGetter();
|
|
26
|
+
if (ws.readyState === ws.OPEN) {
|
|
27
|
+
return resolve({ ws });
|
|
28
|
+
}
|
|
29
|
+
if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
|
|
30
|
+
return resolve({ err: 'ws is closing or closed' });
|
|
31
|
+
}
|
|
32
|
+
ws.addEventListener('open', function onOpen() {
|
|
33
|
+
ws.removeEventListener('open', onOpen);
|
|
34
|
+
resolve({ ws });
|
|
35
|
+
});
|
|
36
|
+
ws.addEventListener('error', function onError(err) {
|
|
37
|
+
ws.removeEventListener('error', onError);
|
|
38
|
+
resolve({ err: err.message });
|
|
39
|
+
});
|
|
40
|
+
ws.addEventListener('close', function onClose(evt) {
|
|
41
|
+
ws.removeEventListener('close', onClose);
|
|
42
|
+
resolve({ err: evt.reason });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
const res = await this.reconnectPromise;
|
|
46
|
+
// only send if we resolved a valid websocket
|
|
47
|
+
if ('ws' in res && res.ws.readyState === res.ws.OPEN) {
|
|
48
|
+
this.ws = res.ws;
|
|
49
|
+
this.ws.onmessage = (msg) => this.onMessage(msg.data.toString());
|
|
50
|
+
this.ws.onclose = () => {
|
|
51
|
+
this.reconnectPromise = undefined;
|
|
52
|
+
this.tryConnect().catch();
|
|
53
|
+
};
|
|
54
|
+
// send outstanding
|
|
55
|
+
for (const id of this.sendQueue) {
|
|
56
|
+
const msg = this.sendBuffer.get(id);
|
|
57
|
+
if (!msg) {
|
|
58
|
+
throw new Error('tried to resend a message we received an ack for');
|
|
59
|
+
}
|
|
60
|
+
this.ws.send(this.codec.toStringBuf(msg));
|
|
61
|
+
}
|
|
62
|
+
this.sendQueue = [];
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// otherwise try and reconnect again
|
|
66
|
+
this.reconnectPromise = undefined;
|
|
67
|
+
setTimeout(() => this.tryConnect(), this.options.retryIntervalMs);
|
|
14
68
|
}
|
|
15
69
|
send(msg) {
|
|
16
70
|
const id = msg.id;
|
|
17
|
-
|
|
71
|
+
if (this.destroyed) {
|
|
72
|
+
throw new Error('ws is destroyed, cant send');
|
|
73
|
+
}
|
|
74
|
+
this.sendBuffer.set(id, msg);
|
|
75
|
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
76
|
+
this.ws.send(this.codec.toStringBuf(msg));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.sendQueue.push(id);
|
|
80
|
+
this.tryConnect().catch();
|
|
81
|
+
}
|
|
18
82
|
return id;
|
|
19
83
|
}
|
|
20
84
|
async close() {
|
|
21
|
-
|
|
85
|
+
this.destroyed = true;
|
|
86
|
+
return this.ws?.close();
|
|
22
87
|
}
|
|
23
88
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
2
|
+
import { describe, test, expect, afterAll } from 'vitest';
|
|
3
|
+
import { createWebSocketServer, createWsTransports, onServerReady, waitForMessage, } from './util';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
const getMsg = () => ({
|
|
6
|
+
id: nanoid(),
|
|
7
|
+
from: 'client',
|
|
8
|
+
to: 'SERVER',
|
|
9
|
+
serviceName: 'test',
|
|
10
|
+
procedureName: 'test',
|
|
11
|
+
payload: {
|
|
12
|
+
msg: 'cool',
|
|
13
|
+
test: Math.random(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
describe('sending and receiving across websockets works', async () => {
|
|
7
17
|
const server = http.createServer();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
await onServerReady(server, port);
|
|
11
|
-
wss = await createWebSocketServer(server);
|
|
12
|
-
});
|
|
18
|
+
const port = await onServerReady(server);
|
|
19
|
+
const wss = await createWebSocketServer(server);
|
|
13
20
|
afterAll(() => {
|
|
14
21
|
wss.clients.forEach((socket) => {
|
|
15
22
|
socket.close();
|
|
@@ -17,25 +24,52 @@ describe('sending and receiving across websockets works', () => {
|
|
|
17
24
|
server.close();
|
|
18
25
|
});
|
|
19
26
|
test('basic send/receive', async () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
to: 'SERVER',
|
|
34
|
-
serviceName: 'test',
|
|
35
|
-
procedureName: 'test',
|
|
36
|
-
payload: msg,
|
|
27
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
28
|
+
const msg = getMsg();
|
|
29
|
+
clientTransport.send(msg);
|
|
30
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg.id)).resolves.toStrictEqual(msg.payload);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('retry logic', async () => {
|
|
34
|
+
const server = http.createServer();
|
|
35
|
+
const port = await onServerReady(server);
|
|
36
|
+
const wss = await createWebSocketServer(server);
|
|
37
|
+
afterAll(() => {
|
|
38
|
+
wss.clients.forEach((socket) => {
|
|
39
|
+
socket.close();
|
|
37
40
|
});
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
server.close();
|
|
42
|
+
});
|
|
43
|
+
// TODO: right now, we only test client-side disconnects, we probably
|
|
44
|
+
// need to also write tests for server-side crashes (but this involves clearing/restoring state)
|
|
45
|
+
// not going to worry about this rn but for future
|
|
46
|
+
test('ws transport is recreated after clean disconnect', async () => {
|
|
47
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
48
|
+
const msg1 = getMsg();
|
|
49
|
+
const msg2 = getMsg();
|
|
50
|
+
clientTransport.send(msg1);
|
|
51
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
52
|
+
clientTransport.ws?.close();
|
|
53
|
+
clientTransport.send(msg2);
|
|
54
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
|
|
55
|
+
});
|
|
56
|
+
test('ws transport is recreated after unclean disconnect', async () => {
|
|
57
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
58
|
+
const msg1 = getMsg();
|
|
59
|
+
const msg2 = getMsg();
|
|
60
|
+
clientTransport.send(msg1);
|
|
61
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
62
|
+
clientTransport.ws?.terminate();
|
|
63
|
+
clientTransport.send(msg2);
|
|
64
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
|
|
65
|
+
});
|
|
66
|
+
test('ws transport is not recreated after manually closing', async () => {
|
|
67
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
68
|
+
const msg1 = getMsg();
|
|
69
|
+
const msg2 = getMsg();
|
|
70
|
+
clientTransport.send(msg1);
|
|
71
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
72
|
+
clientTransport.close();
|
|
73
|
+
return expect(() => clientTransport.send(msg2)).toThrow(new Error('ws is destroyed, cant send'));
|
|
40
74
|
});
|
|
41
75
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@replit/river",
|
|
3
3
|
"sideEffects": false,
|
|
4
4
|
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"types": "index.d.ts",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"build": "tsc",
|
|
30
30
|
"prepack": "npm run build",
|
|
31
31
|
"release": "npm publish --access public",
|
|
32
|
-
"test": "vitest"
|
|
32
|
+
"test": "vitest",
|
|
33
|
+
"bench": "vitest bench"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=16"
|