@replit/river 0.9.2 → 0.10.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.js +11 -11
- package/dist/__tests__/cleanup.test.d.ts +2 -0
- package/dist/__tests__/cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/{invariants.test.js → cleanup.test.js} +47 -20
- package/dist/__tests__/disconnects.test.d.ts +2 -0
- package/dist/__tests__/disconnects.test.d.ts.map +1 -0
- package/dist/__tests__/disconnects.test.js +163 -0
- package/dist/__tests__/e2e.test.js +33 -32
- package/dist/__tests__/fixtures/cleanup.d.ts +2 -2
- package/dist/__tests__/fixtures/cleanup.d.ts.map +1 -1
- package/dist/__tests__/fixtures/cleanup.js +6 -9
- package/dist/__tests__/fixtures/services.d.ts +36 -36
- package/dist/__tests__/fixtures/services.d.ts.map +1 -1
- package/dist/__tests__/fixtures/services.js +36 -53
- package/dist/__tests__/handler.test.js +6 -7
- package/dist/__tests__/typescript-stress.test.d.ts +149 -149
- package/dist/__tests__/typescript-stress.test.d.ts.map +1 -1
- package/dist/__tests__/typescript-stress.test.js +14 -14
- package/dist/router/builder.d.ts +6 -7
- package/dist/router/builder.d.ts.map +1 -1
- package/dist/router/client.d.ts +7 -3
- package/dist/router/client.d.ts.map +1 -1
- package/dist/router/client.js +204 -106
- package/dist/router/defs.d.ts +16 -0
- package/dist/router/defs.d.ts.map +1 -0
- package/dist/router/defs.js +11 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1 -0
- package/dist/router/result.d.ts +2 -1
- package/dist/router/result.d.ts.map +1 -1
- package/dist/router/result.js +5 -1
- package/dist/router/server.d.ts +5 -5
- package/dist/router/server.d.ts.map +1 -1
- package/dist/router/server.js +125 -82
- package/dist/transport/impls/stdio/stdio.test.js +1 -2
- package/dist/transport/impls/ws/client.d.ts +1 -4
- package/dist/transport/impls/ws/client.d.ts.map +1 -1
- package/dist/transport/impls/ws/client.js +5 -6
- package/dist/transport/impls/ws/server.d.ts +3 -0
- package/dist/transport/impls/ws/server.d.ts.map +1 -1
- package/dist/transport/impls/ws/server.js +28 -23
- package/dist/transport/impls/ws/ws.test.js +84 -16
- package/dist/transport/index.d.ts +0 -9
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/index.js +0 -21
- package/dist/transport/message.d.ts +3 -4
- package/dist/transport/message.d.ts.map +1 -1
- package/dist/util/testHelpers.d.ts +20 -97
- package/dist/util/testHelpers.d.ts.map +1 -1
- package/dist/util/testHelpers.js +94 -249
- package/package.json +14 -13
- package/dist/__tests__/invariants.test.d.ts +0 -2
- package/dist/__tests__/invariants.test.d.ts.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { assert, bench, describe } from 'vitest';
|
|
3
|
-
import { createWebSocketServer, createWsTransports, onServerReady, } from '../util/testHelpers';
|
|
3
|
+
import { createWebSocketServer, createWsTransports, onServerReady, waitForMessage, } from '../util/testHelpers';
|
|
4
4
|
import largePayload from './fixtures/largePayload.json';
|
|
5
5
|
import { TestServiceConstructor } from './fixtures/services';
|
|
6
6
|
import { createServer } from '../router/server';
|
|
7
7
|
import { createClient } from '../router/client';
|
|
8
8
|
import { StupidlyLargeService } from './typescript-stress.test';
|
|
9
|
-
import {
|
|
9
|
+
import { buildServiceDefs } from '../router/defs';
|
|
10
10
|
let smallId = 0;
|
|
11
11
|
let largeId = 0;
|
|
12
12
|
const dummyPayloadSmall = () => ({
|
|
@@ -53,8 +53,8 @@ describe('simple router level bandwidth', async () => {
|
|
|
53
53
|
const port = await onServerReady(httpServer);
|
|
54
54
|
const webSocketServer = await createWebSocketServer(httpServer);
|
|
55
55
|
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
56
|
-
const serviceDefs =
|
|
57
|
-
const server =
|
|
56
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
57
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
58
58
|
const client = createClient(clientTransport);
|
|
59
59
|
bench('rpc (wait for response)', async () => {
|
|
60
60
|
const result = await client.test.add.rpc({ n: 1 });
|
|
@@ -75,13 +75,13 @@ describe('complex (50 procedures) router level bandwidth', async () => {
|
|
|
75
75
|
const port = await onServerReady(httpServer);
|
|
76
76
|
const webSocketServer = await createWebSocketServer(httpServer);
|
|
77
77
|
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
78
|
-
const serviceDefs =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const server =
|
|
78
|
+
const serviceDefs = buildServiceDefs([
|
|
79
|
+
StupidlyLargeService('a'),
|
|
80
|
+
StupidlyLargeService('b'),
|
|
81
|
+
StupidlyLargeService('c'),
|
|
82
|
+
StupidlyLargeService('d'),
|
|
83
|
+
]);
|
|
84
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
85
85
|
const client = createClient(clientTransport);
|
|
86
86
|
bench('rpc (wait for response)', async () => {
|
|
87
87
|
const result = await client.b.f35.rpc({ a: 1 });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.test.d.ts","sourceRoot":"","sources":["../../__tests__/cleanup.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { afterAll, assert, describe, expect, test } from 'vitest';
|
|
2
2
|
import http from 'http';
|
|
3
3
|
import { createWebSocketServer, createWsTransports, iterNext, onServerReady, } from '../util/testHelpers';
|
|
4
|
-
import { SubscribableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
|
|
4
|
+
import { SubscribableServiceConstructor, TestServiceConstructor, UploadableServiceConstructor, } from './fixtures/services';
|
|
5
5
|
import { createClient, createServer } from '../router';
|
|
6
|
-
import { ensureServerIsClean, ensureTransportQueuesAreEventuallyEmpty,
|
|
6
|
+
import { ensureServerIsClean, ensureTransportQueuesAreEventuallyEmpty, waitFor, } from './fixtures/cleanup';
|
|
7
|
+
import { buildServiceDefs } from '../router/defs';
|
|
7
8
|
describe('procedures should leave no trace after finishing', async () => {
|
|
8
9
|
const httpServer = http.createServer();
|
|
9
10
|
const port = await onServerReady(httpServer);
|
|
@@ -15,8 +16,8 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
15
16
|
});
|
|
16
17
|
test('closing a transport from the client cleans up connection on the server', async () => {
|
|
17
18
|
const [clientTransport, serverTransport] = getTransports();
|
|
18
|
-
const serviceDefs =
|
|
19
|
-
const server =
|
|
19
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
20
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
20
21
|
const client = createClient(clientTransport);
|
|
21
22
|
expect(clientTransport.connections.size).toEqual(0);
|
|
22
23
|
expect(serverTransport.connections.size).toEqual(0);
|
|
@@ -28,12 +29,12 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
28
29
|
// should be back to 0 connections after client closes
|
|
29
30
|
clientTransport.close();
|
|
30
31
|
expect(clientTransport.connections.size).toEqual(0);
|
|
31
|
-
await
|
|
32
|
+
await waitFor(() => expect(serverTransport.connections.size, 'server should cleanup connection after client closes').toEqual(0));
|
|
32
33
|
});
|
|
33
34
|
test('closing a transport from the server cleans up connection on the client', async () => {
|
|
34
35
|
const [clientTransport, serverTransport] = getTransports();
|
|
35
|
-
const serviceDefs =
|
|
36
|
-
const server =
|
|
36
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
37
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
37
38
|
const client = createClient(clientTransport);
|
|
38
39
|
expect(clientTransport.connections.size).toEqual(0);
|
|
39
40
|
expect(serverTransport.connections.size).toEqual(0);
|
|
@@ -45,12 +46,12 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
45
46
|
// should be back to 0 connections after client closes
|
|
46
47
|
serverTransport.close();
|
|
47
48
|
expect(serverTransport.connections.size).toEqual(0);
|
|
48
|
-
await
|
|
49
|
+
await waitFor(() => expect(clientTransport.connections.size, 'client should cleanup connection after server closes').toEqual(0));
|
|
49
50
|
});
|
|
50
51
|
test('rpc', async () => {
|
|
51
52
|
const [clientTransport, serverTransport] = getTransports();
|
|
52
|
-
const serviceDefs =
|
|
53
|
-
const server =
|
|
53
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
54
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
54
55
|
const client = createClient(clientTransport);
|
|
55
56
|
let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
|
|
56
57
|
let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
|
|
@@ -70,8 +71,8 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
70
71
|
});
|
|
71
72
|
test('stream', async () => {
|
|
72
73
|
const [clientTransport, serverTransport] = getTransports();
|
|
73
|
-
const serviceDefs =
|
|
74
|
-
const server =
|
|
74
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
75
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
75
76
|
const client = createClient(clientTransport);
|
|
76
77
|
let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
|
|
77
78
|
let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
|
|
@@ -83,10 +84,10 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
83
84
|
assert(result1.ok);
|
|
84
85
|
expect(result1.payload).toStrictEqual({ response: '1' });
|
|
85
86
|
// ensure we only have one stream despite pushing multiple messages.
|
|
86
|
-
await
|
|
87
|
+
await waitFor(() => expect(server.streams.size).toEqual(1));
|
|
87
88
|
input.end();
|
|
88
89
|
// ensure we no longer have any streams since the input was closed.
|
|
89
|
-
await
|
|
90
|
+
await waitFor(() => expect(server.streams.size).toEqual(0));
|
|
90
91
|
const result2 = await iterNext(output);
|
|
91
92
|
assert(result2.ok);
|
|
92
93
|
expect(result2.payload).toStrictEqual({ response: '2' });
|
|
@@ -107,24 +108,50 @@ describe('procedures should leave no trace after finishing', async () => {
|
|
|
107
108
|
});
|
|
108
109
|
test('subscription', async () => {
|
|
109
110
|
const [clientTransport, serverTransport] = getTransports();
|
|
110
|
-
const serviceDefs =
|
|
111
|
-
const server =
|
|
111
|
+
const serviceDefs = buildServiceDefs([SubscribableServiceConstructor()]);
|
|
112
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
112
113
|
const client = createClient(clientTransport);
|
|
113
114
|
let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
|
|
114
115
|
let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
|
|
115
116
|
// start procedure
|
|
116
|
-
const [subscription, close] = await client.
|
|
117
|
+
const [subscription, close] = await client.subscribable.value.subscribe({});
|
|
117
118
|
let result = await iterNext(subscription);
|
|
118
119
|
assert(result.ok);
|
|
119
120
|
expect(result.payload).toStrictEqual({ result: 0 });
|
|
120
|
-
const add1 = await client.
|
|
121
|
+
const add1 = await client.subscribable.add.rpc({ n: 1 });
|
|
121
122
|
assert(add1.ok);
|
|
122
123
|
result = await iterNext(subscription);
|
|
123
124
|
assert(result.ok);
|
|
124
|
-
expect(result.payload).toStrictEqual({ result: 1 });
|
|
125
125
|
close();
|
|
126
126
|
// end procedure
|
|
127
|
-
// number of message handlers shouldn't increase after
|
|
127
|
+
// number of message handlers shouldn't increase after subscription ends
|
|
128
|
+
expect(serverTransport.eventDispatcher.numberOfListeners('message')).toEqual(serverListeners);
|
|
129
|
+
expect(clientTransport.eventDispatcher.numberOfListeners('message')).toEqual(clientListeners);
|
|
130
|
+
// check number of connections
|
|
131
|
+
expect(serverTransport.connections.size).toEqual(1);
|
|
132
|
+
expect(clientTransport.connections.size).toEqual(1);
|
|
133
|
+
await ensureTransportQueuesAreEventuallyEmpty(clientTransport);
|
|
134
|
+
await ensureTransportQueuesAreEventuallyEmpty(serverTransport);
|
|
135
|
+
// ensure we have no streams left on the server
|
|
136
|
+
await ensureServerIsClean(server);
|
|
137
|
+
});
|
|
138
|
+
test('upload', async () => {
|
|
139
|
+
const [clientTransport, serverTransport] = getTransports();
|
|
140
|
+
const serviceDefs = buildServiceDefs([UploadableServiceConstructor()]);
|
|
141
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
142
|
+
const client = createClient(clientTransport);
|
|
143
|
+
let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
|
|
144
|
+
let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
|
|
145
|
+
// start procedure
|
|
146
|
+
const [addStream, addResult] = await client.uploadable.addMultiple.upload();
|
|
147
|
+
addStream.push({ n: 1 });
|
|
148
|
+
addStream.push({ n: 2 });
|
|
149
|
+
addStream.end();
|
|
150
|
+
const result = await addResult;
|
|
151
|
+
assert(result.ok);
|
|
152
|
+
expect(result.payload).toStrictEqual({ result: 3 });
|
|
153
|
+
// end procedure
|
|
154
|
+
// number of message handlers shouldn't increase after upload ends
|
|
128
155
|
expect(serverTransport.eventDispatcher.numberOfListeners('message')).toEqual(serverListeners);
|
|
129
156
|
expect(clientTransport.eventDispatcher.numberOfListeners('message')).toEqual(clientListeners);
|
|
130
157
|
// check number of connections
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disconnects.test.d.ts","sourceRoot":"","sources":["../../__tests__/disconnects.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { afterAll, afterEach, assert, beforeEach, describe, expect, test, vi, } from 'vitest';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import { createLocalWebSocketClient, createWebSocketServer, createWsTransports, iterNext, onServerReady, } from '../util/testHelpers';
|
|
4
|
+
import { SubscribableServiceConstructor, TestServiceConstructor, UploadableServiceConstructor, } from './fixtures/services';
|
|
5
|
+
import { createClient, createServer } from '../router';
|
|
6
|
+
import { ensureServerIsClean, waitFor } from './fixtures/cleanup';
|
|
7
|
+
import { CONNECTION_GRACE_PERIOD_MS } from '../router/client';
|
|
8
|
+
import { Err, UNEXPECTED_DISCONNECT } from '../router/result';
|
|
9
|
+
import { WebSocketServerTransport } from '../transport/impls/ws/server';
|
|
10
|
+
import { WebSocketClientTransport } from '../transport/impls/ws/client';
|
|
11
|
+
import { buildServiceDefs } from '../router/defs';
|
|
12
|
+
describe('procedures should handle unexpected disconnects', async () => {
|
|
13
|
+
const httpServer = http.createServer();
|
|
14
|
+
const port = await onServerReady(httpServer);
|
|
15
|
+
const webSocketServer = await createWebSocketServer(httpServer);
|
|
16
|
+
const getTransports = () => createWsTransports(port, webSocketServer);
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
webSocketServer.close();
|
|
19
|
+
httpServer.close();
|
|
20
|
+
});
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.useFakeTimers();
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
test('rpc', async () => {
|
|
28
|
+
const [clientTransport, serverTransport] = getTransports();
|
|
29
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
30
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
31
|
+
const client = createClient(clientTransport);
|
|
32
|
+
// start procedure
|
|
33
|
+
await client.test.add.rpc({ n: 3 });
|
|
34
|
+
expect(clientTransport.connections.size).toEqual(1);
|
|
35
|
+
expect(serverTransport.connections.size).toEqual(1);
|
|
36
|
+
clientTransport.connections.forEach((conn) => conn.ws.close());
|
|
37
|
+
clientTransport.tryReconnecting = false;
|
|
38
|
+
const procPromise = client.test.add.rpc({ n: 4 });
|
|
39
|
+
// end procedure
|
|
40
|
+
// after we've disconnected, hit end of grace period
|
|
41
|
+
await vi.runOnlyPendingTimersAsync();
|
|
42
|
+
await vi.advanceTimersByTimeAsync(CONNECTION_GRACE_PERIOD_MS);
|
|
43
|
+
// we should get an error + expect the streams to be cleaned up
|
|
44
|
+
await expect(procPromise).resolves.toMatchObject(Err({
|
|
45
|
+
code: UNEXPECTED_DISCONNECT,
|
|
46
|
+
}));
|
|
47
|
+
waitFor(() => expect(clientTransport.connections.size).toEqual(0));
|
|
48
|
+
waitFor(() => expect(serverTransport.connections.size).toEqual(0));
|
|
49
|
+
await ensureServerIsClean(server);
|
|
50
|
+
});
|
|
51
|
+
test('stream', async () => {
|
|
52
|
+
const [clientTransport, serverTransport] = getTransports();
|
|
53
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
54
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
55
|
+
const client = createClient(clientTransport);
|
|
56
|
+
// start procedure
|
|
57
|
+
const [input, output] = await client.test.echo.stream();
|
|
58
|
+
input.push({ msg: 'abc', ignore: false });
|
|
59
|
+
const result = await iterNext(output);
|
|
60
|
+
assert(result.ok);
|
|
61
|
+
expect(clientTransport.connections.size).toEqual(1);
|
|
62
|
+
expect(serverTransport.connections.size).toEqual(1);
|
|
63
|
+
clientTransport.connections.forEach((conn) => conn.ws.close());
|
|
64
|
+
clientTransport.tryReconnecting = false;
|
|
65
|
+
const nextResPromise = iterNext(output);
|
|
66
|
+
// end procedure
|
|
67
|
+
// after we've disconnected, hit end of grace period
|
|
68
|
+
await vi.runOnlyPendingTimersAsync();
|
|
69
|
+
await vi.advanceTimersByTimeAsync(CONNECTION_GRACE_PERIOD_MS);
|
|
70
|
+
// we should get an error + expect the streams to be cleaned up
|
|
71
|
+
await expect(nextResPromise).resolves.toMatchObject(Err({
|
|
72
|
+
code: UNEXPECTED_DISCONNECT,
|
|
73
|
+
}));
|
|
74
|
+
waitFor(() => expect(clientTransport.connections.size).toEqual(0));
|
|
75
|
+
waitFor(() => expect(serverTransport.connections.size).toEqual(0));
|
|
76
|
+
await ensureServerIsClean(server);
|
|
77
|
+
});
|
|
78
|
+
test('subscription', async () => {
|
|
79
|
+
const serverTransport = new WebSocketServerTransport(webSocketServer, 'SERVER');
|
|
80
|
+
const client1Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client1', 'SERVER');
|
|
81
|
+
const client2Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client2', 'SERVER');
|
|
82
|
+
const serviceDefs = buildServiceDefs([SubscribableServiceConstructor()]);
|
|
83
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
84
|
+
const client1 = createClient(client1Transport);
|
|
85
|
+
const client2 = createClient(client2Transport);
|
|
86
|
+
// start procedure
|
|
87
|
+
// client1 and client2 both subscribe
|
|
88
|
+
const [subscription1, close1] = await client1.subscribable.value.subscribe({});
|
|
89
|
+
let result = await iterNext(subscription1);
|
|
90
|
+
assert(result.ok);
|
|
91
|
+
expect(result.payload).toStrictEqual({ result: 0 });
|
|
92
|
+
const [subscription2, _close2] = await client2.subscribable.value.subscribe({});
|
|
93
|
+
result = await iterNext(subscription2);
|
|
94
|
+
assert(result.ok);
|
|
95
|
+
expect(result.payload).toStrictEqual({ result: 0 });
|
|
96
|
+
// client2 adds a value
|
|
97
|
+
const add1 = await client2.subscribable.add.rpc({ n: 1 });
|
|
98
|
+
assert(add1.ok);
|
|
99
|
+
// both clients should receive the updated value
|
|
100
|
+
result = await iterNext(subscription1);
|
|
101
|
+
assert(result.ok);
|
|
102
|
+
expect(result.payload).toStrictEqual({ result: 1 });
|
|
103
|
+
result = await iterNext(subscription2);
|
|
104
|
+
assert(result.ok);
|
|
105
|
+
expect(result.payload).toStrictEqual({ result: 1 });
|
|
106
|
+
// all clients are connected
|
|
107
|
+
expect(client1Transport.connections.size).toEqual(1);
|
|
108
|
+
expect(client2Transport.connections.size).toEqual(1);
|
|
109
|
+
expect(serverTransport.connections.size).toEqual(2);
|
|
110
|
+
// kill the connection for client2
|
|
111
|
+
client2Transport.connections.forEach((conn) => conn.ws.close());
|
|
112
|
+
client2Transport.tryReconnecting = false;
|
|
113
|
+
// client1 who is still connected can still add values and receive updates
|
|
114
|
+
const add2Promise = client1.subscribable.add.rpc({ n: 2 });
|
|
115
|
+
// after we've disconnected, hit end of grace period
|
|
116
|
+
await vi.runOnlyPendingTimersAsync();
|
|
117
|
+
await vi.advanceTimersByTimeAsync(CONNECTION_GRACE_PERIOD_MS);
|
|
118
|
+
// we should get an error from the subscription on client2
|
|
119
|
+
const nextResPromise = iterNext(subscription2);
|
|
120
|
+
await expect(nextResPromise).resolves.toMatchObject(Err({
|
|
121
|
+
code: UNEXPECTED_DISCONNECT,
|
|
122
|
+
}));
|
|
123
|
+
// client1 who is still connected can still add values and receive updates
|
|
124
|
+
assert((await add2Promise).ok);
|
|
125
|
+
result = await iterNext(subscription1);
|
|
126
|
+
assert(result.ok);
|
|
127
|
+
expect(result.payload).toStrictEqual({ result: 3 });
|
|
128
|
+
// at this point, only client1 is connected
|
|
129
|
+
expect(client1Transport.connections.size).toEqual(1);
|
|
130
|
+
expect(client2Transport.connections.size).toEqual(0);
|
|
131
|
+
expect(serverTransport.connections.size).toEqual(1);
|
|
132
|
+
// cleanup client1 (client2 is already disconnected)
|
|
133
|
+
close1();
|
|
134
|
+
await client1Transport.close();
|
|
135
|
+
await ensureServerIsClean(server);
|
|
136
|
+
});
|
|
137
|
+
test('upload', async () => {
|
|
138
|
+
const [clientTransport, serverTransport] = getTransports();
|
|
139
|
+
const serviceDefs = buildServiceDefs([UploadableServiceConstructor()]);
|
|
140
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
141
|
+
const client = createClient(clientTransport);
|
|
142
|
+
// start procedure
|
|
143
|
+
const [addStream, addResult] = await client.uploadable.addMultiple.upload();
|
|
144
|
+
addStream.push({ n: 1 });
|
|
145
|
+
addStream.push({ n: 2 });
|
|
146
|
+
// end procedure
|
|
147
|
+
// need to wait for connection to be established
|
|
148
|
+
await waitFor(() => expect(clientTransport.connections.size).toEqual(1));
|
|
149
|
+
await waitFor(() => expect(serverTransport.connections.size).toEqual(1));
|
|
150
|
+
clientTransport.connections.forEach((conn) => conn.ws.close());
|
|
151
|
+
clientTransport.tryReconnecting = false;
|
|
152
|
+
// after we've disconnected, hit end of grace period
|
|
153
|
+
await vi.runOnlyPendingTimersAsync();
|
|
154
|
+
await vi.advanceTimersByTimeAsync(CONNECTION_GRACE_PERIOD_MS);
|
|
155
|
+
// we should get an error + expect the streams to be cleaned up
|
|
156
|
+
await expect(addResult).resolves.toMatchObject(Err({
|
|
157
|
+
code: UNEXPECTED_DISCONNECT,
|
|
158
|
+
}));
|
|
159
|
+
waitFor(() => expect(clientTransport.connections.size).toEqual(0));
|
|
160
|
+
waitFor(() => expect(serverTransport.connections.size).toEqual(0));
|
|
161
|
+
await ensureServerIsClean(server);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -9,6 +9,7 @@ import { codecs } from '../codec/codec.test';
|
|
|
9
9
|
import { WebSocketClientTransport } from '../transport/impls/ws/client';
|
|
10
10
|
import { WebSocketServerTransport } from '../transport/impls/ws/server';
|
|
11
11
|
import { testFinishesCleanly } from './fixtures/cleanup';
|
|
12
|
+
import { buildServiceDefs } from '../router/defs';
|
|
12
13
|
describe.each(codecs)('client <-> server integration test ($name codec)', async ({ codec }) => {
|
|
13
14
|
const httpServer = http.createServer();
|
|
14
15
|
const port = await onServerReady(httpServer);
|
|
@@ -20,8 +21,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
20
21
|
});
|
|
21
22
|
test('rpc', async () => {
|
|
22
23
|
const [clientTransport, serverTransport] = getTransports();
|
|
23
|
-
const serviceDefs =
|
|
24
|
-
const server =
|
|
24
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
25
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
25
26
|
const client = createClient(clientTransport);
|
|
26
27
|
const result = await client.test.add.rpc({ n: 3 });
|
|
27
28
|
assert(result.ok);
|
|
@@ -34,13 +35,13 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
34
35
|
});
|
|
35
36
|
test('fallible rpc', async () => {
|
|
36
37
|
const [clientTransport, serverTransport] = getTransports();
|
|
37
|
-
const serviceDefs =
|
|
38
|
-
const server =
|
|
38
|
+
const serviceDefs = buildServiceDefs([FallibleServiceConstructor()]);
|
|
39
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
39
40
|
const client = createClient(clientTransport);
|
|
40
|
-
const result = await client.
|
|
41
|
+
const result = await client.fallible.divide.rpc({ a: 10, b: 2 });
|
|
41
42
|
assert(result.ok);
|
|
42
43
|
expect(result.payload).toStrictEqual({ result: 5 });
|
|
43
|
-
const result2 = await client.
|
|
44
|
+
const result2 = await client.fallible.divide.rpc({ a: 10, b: 0 });
|
|
44
45
|
assert(!result2.ok);
|
|
45
46
|
expect(result2.payload).toStrictEqual({
|
|
46
47
|
code: DIV_BY_ZERO,
|
|
@@ -57,10 +58,10 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
57
58
|
});
|
|
58
59
|
test('rpc with binary (uint8array)', async () => {
|
|
59
60
|
const [clientTransport, serverTransport] = getTransports();
|
|
60
|
-
const serviceDefs =
|
|
61
|
-
const server =
|
|
61
|
+
const serviceDefs = buildServiceDefs([BinaryFileServiceConstructor()]);
|
|
62
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
62
63
|
const client = createClient(clientTransport);
|
|
63
|
-
const result = await client.
|
|
64
|
+
const result = await client.bin.getFile.rpc({ file: 'test.py' });
|
|
64
65
|
assert(result.ok);
|
|
65
66
|
assert(result.payload.contents instanceof Uint8Array);
|
|
66
67
|
expect(new TextDecoder().decode(result.payload.contents)).toStrictEqual('contents for file test.py');
|
|
@@ -72,8 +73,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
72
73
|
});
|
|
73
74
|
test('stream', async () => {
|
|
74
75
|
const [clientTransport, serverTransport] = getTransports();
|
|
75
|
-
const serviceDefs =
|
|
76
|
-
const server =
|
|
76
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
77
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
77
78
|
const client = createClient(clientTransport);
|
|
78
79
|
const [input, output, close] = await client.test.echo.stream();
|
|
79
80
|
input.push({ msg: 'abc', ignore: false });
|
|
@@ -102,8 +103,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
102
103
|
});
|
|
103
104
|
test('stream with init message', async () => {
|
|
104
105
|
const [clientTransport, serverTransport] = getTransports();
|
|
105
|
-
const serviceDefs =
|
|
106
|
-
const server =
|
|
106
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
107
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
107
108
|
const client = createClient(clientTransport);
|
|
108
109
|
const [input, output, close] = await client.test.echoWithPrefix.stream({
|
|
109
110
|
prefix: 'test',
|
|
@@ -127,10 +128,10 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
127
128
|
});
|
|
128
129
|
test('fallible stream', async () => {
|
|
129
130
|
const [clientTransport, serverTransport] = getTransports();
|
|
130
|
-
const serviceDefs =
|
|
131
|
-
const server =
|
|
131
|
+
const serviceDefs = buildServiceDefs([FallibleServiceConstructor()]);
|
|
132
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
132
133
|
const client = createClient(clientTransport);
|
|
133
|
-
const [input, output, close] = await client.
|
|
134
|
+
const [input, output, close] = await client.fallible.echo.stream();
|
|
134
135
|
input.push({ msg: 'abc', throwResult: false, throwError: false });
|
|
135
136
|
const result1 = await iterNext(output);
|
|
136
137
|
assert(result1 && result1.ok);
|
|
@@ -158,19 +159,19 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
158
159
|
const serverTransport = new WebSocketServerTransport(webSocketServer, 'SERVER', options);
|
|
159
160
|
const client1Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client1', 'SERVER', options);
|
|
160
161
|
const client2Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client2', 'SERVER', options);
|
|
161
|
-
const serviceDefs =
|
|
162
|
-
const server =
|
|
162
|
+
const serviceDefs = buildServiceDefs([SubscribableServiceConstructor()]);
|
|
163
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
163
164
|
const client1 = createClient(client1Transport);
|
|
164
165
|
const client2 = createClient(client2Transport);
|
|
165
|
-
const [subscription1, close1] = await client1.
|
|
166
|
+
const [subscription1, close1] = await client1.subscribable.value.subscribe({});
|
|
166
167
|
let result = await iterNext(subscription1);
|
|
167
168
|
assert(result.ok);
|
|
168
169
|
expect(result.payload).toStrictEqual({ result: 0 });
|
|
169
|
-
const [subscription2, close2] = await client2.
|
|
170
|
+
const [subscription2, close2] = await client2.subscribable.value.subscribe({});
|
|
170
171
|
result = await iterNext(subscription2);
|
|
171
172
|
assert(result.ok);
|
|
172
173
|
expect(result.payload).toStrictEqual({ result: 0 });
|
|
173
|
-
const add1 = await client1.
|
|
174
|
+
const add1 = await client1.subscribable.add.rpc({ n: 1 });
|
|
174
175
|
assert(add1.ok);
|
|
175
176
|
result = await iterNext(subscription1);
|
|
176
177
|
assert(result.ok);
|
|
@@ -178,7 +179,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
178
179
|
result = await iterNext(subscription2);
|
|
179
180
|
assert(result.ok);
|
|
180
181
|
expect(result.payload).toStrictEqual({ result: 1 });
|
|
181
|
-
const add2 = await client2.
|
|
182
|
+
const add2 = await client2.subscribable.add.rpc({ n: 3 });
|
|
182
183
|
assert(add2.ok);
|
|
183
184
|
result = await iterNext(subscription1);
|
|
184
185
|
assert(result.ok);
|
|
@@ -196,8 +197,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
196
197
|
});
|
|
197
198
|
test('upload', async () => {
|
|
198
199
|
const [clientTransport, serverTransport] = getTransports();
|
|
199
|
-
const serviceDefs =
|
|
200
|
-
const server =
|
|
200
|
+
const serviceDefs = buildServiceDefs([UploadableServiceConstructor()]);
|
|
201
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
201
202
|
const client = createClient(clientTransport);
|
|
202
203
|
const [addStream, addResult] = await client.uploadable.addMultiple.upload();
|
|
203
204
|
addStream.push({ n: 1 });
|
|
@@ -214,8 +215,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
214
215
|
});
|
|
215
216
|
test('upload with init message', async () => {
|
|
216
217
|
const [clientTransport, serverTransport] = getTransports();
|
|
217
|
-
const serviceDefs =
|
|
218
|
-
const server =
|
|
218
|
+
const serviceDefs = buildServiceDefs([UploadableServiceConstructor()]);
|
|
219
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
219
220
|
const client = createClient(clientTransport);
|
|
220
221
|
const [addStream, addResult] = await client.uploadable.addMultipleWithPrefix.upload({
|
|
221
222
|
prefix: 'test',
|
|
@@ -234,8 +235,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
234
235
|
});
|
|
235
236
|
test('message order is preserved in the face of disconnects', async () => {
|
|
236
237
|
const [clientTransport, serverTransport] = getTransports();
|
|
237
|
-
const serviceDefs =
|
|
238
|
-
const server =
|
|
238
|
+
const serviceDefs = buildServiceDefs([OrderingServiceConstructor()]);
|
|
239
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
239
240
|
const client = createClient(clientTransport);
|
|
240
241
|
const expected = [];
|
|
241
242
|
for (let i = 0; i < 50; i++) {
|
|
@@ -262,8 +263,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
262
263
|
const CONCURRENCY = 10;
|
|
263
264
|
test('concurrent rpcs', async () => {
|
|
264
265
|
const [clientTransport, serverTransport] = getTransports();
|
|
265
|
-
const serviceDefs =
|
|
266
|
-
const server =
|
|
266
|
+
const serviceDefs = buildServiceDefs([OrderingServiceConstructor()]);
|
|
267
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
267
268
|
const client = createClient(clientTransport);
|
|
268
269
|
const promises = [];
|
|
269
270
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
@@ -282,8 +283,8 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
282
283
|
});
|
|
283
284
|
test('concurrent streams', async () => {
|
|
284
285
|
const [clientTransport, serverTransport] = getTransports();
|
|
285
|
-
const serviceDefs =
|
|
286
|
-
const server =
|
|
286
|
+
const serviceDefs = buildServiceDefs([TestServiceConstructor()]);
|
|
287
|
+
const server = createServer(serverTransport, serviceDefs);
|
|
287
288
|
const client = createClient(clientTransport);
|
|
288
289
|
const openStreams = [];
|
|
289
290
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Connection, Transport } from '../../transport';
|
|
2
2
|
import { Server } from '../../router';
|
|
3
3
|
export declare function ensureTransportIsClean(t: Transport<Connection>): Promise<void>;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function waitFor<T>(cb: () => T | Promise<T>): Promise<T>;
|
|
5
5
|
export declare function ensureTransportQueuesAreEventuallyEmpty(t: Transport<Connection>): Promise<void>;
|
|
6
|
-
export declare function ensureServerIsClean(s: Server<unknown>): Promise<
|
|
6
|
+
export declare function ensureServerIsClean(s: Server<unknown>): Promise<void>;
|
|
7
7
|
export declare function testFinishesCleanly({ clientTransports, serverTransport, server, }: Partial<{
|
|
8
8
|
clientTransports: Array<Transport<Connection>>;
|
|
9
9
|
serverTransport: Transport<Connection>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,iBAiBpE;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,cAElD;AAED,wBAAsB,uCAAuC,CAC3D,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,iBAczB;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,iBAO3D;AAED,wBAAsB,mBAAmB,CAAC,EACxC,gBAAgB,EAChB,eAAe,EACf,MAAM,GACP,EAAE,OAAO,CAAC;IACT,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC/C,eAAe,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;CACzB,CAAC,iBAgBD"}
|
|
@@ -7,20 +7,17 @@ export async function ensureTransportIsClean(t) {
|
|
|
7
7
|
expect(t.state, `transport ${t.clientId} should be closed after the test`).to.not.equal('open');
|
|
8
8
|
expect(t.connections, `transport ${t.clientId} should not have open connections after the test`).toStrictEqual(new Map());
|
|
9
9
|
expect(t.eventDispatcher.numberOfListeners('message'), `transport ${t.clientId} should not have open message handlers after the test`).equal(0);
|
|
10
|
+
expect(t.eventDispatcher.numberOfListeners('connectionStatus'), `transport ${t.clientId} should not have open connection handlers after the test`).equal(0);
|
|
10
11
|
}
|
|
11
|
-
export
|
|
12
|
-
return vi
|
|
13
|
-
.waitUntil(() => valueGetter() === expected, waitUntilOptions)
|
|
14
|
-
.finally(() => {
|
|
15
|
-
expect(valueGetter(), message).toEqual(expected);
|
|
16
|
-
});
|
|
12
|
+
export function waitFor(cb) {
|
|
13
|
+
return vi.waitFor(cb, waitUntilOptions);
|
|
17
14
|
}
|
|
18
15
|
export async function ensureTransportQueuesAreEventuallyEmpty(t) {
|
|
19
|
-
await
|
|
20
|
-
await
|
|
16
|
+
await waitFor(() => expect(t.sendQueue.size, `transport ${t.clientId} should not have any messages waiting to send after the test`).toEqual(0));
|
|
17
|
+
await waitFor(() => expect(t.sendBuffer.size, `transport ${t.clientId} should not have any un-acked messages after the test`).toEqual(0));
|
|
21
18
|
}
|
|
22
19
|
export async function ensureServerIsClean(s) {
|
|
23
|
-
return
|
|
20
|
+
return waitFor(() => expect(s.streams.size, `server should not have any open streams after the test`).toEqual(0));
|
|
24
21
|
}
|
|
25
22
|
export async function testFinishesCleanly({ clientTransports, serverTransport, server, }) {
|
|
26
23
|
if (clientTransports) {
|