@replit/river 0.1.10 → 0.2.1

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 CHANGED
@@ -6,13 +6,3 @@ It's like tRPC but...
6
6
  - with full-duplex streaming
7
7
  - with support for service multiplexing
8
8
  - over WebSockets
9
-
10
- ## Levels of abstraction
11
-
12
- - Router
13
- - Service
14
- - Procedure
15
-
16
- ## TODO
17
-
18
- - support broadcast
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import http from 'http';
2
+ import { bench, describe } from 'vitest';
3
+ import { createWebSocketServer, createWsTransports, onServerReady, waitForMessage, } from '../transport/util';
4
+ import largePayload from './largePayload.json';
5
+ import { TestServiceConstructor } from './integration.test';
6
+ import { createServer } from '../router/server';
7
+ import { createClient } from '../router/client';
8
+ import { StupidlyLargeService } from './typescript-stress.test';
9
+ let smallId = 0;
10
+ let largeId = 0;
11
+ const dummyPayloadSmall = () => ({
12
+ id: `${smallId++}`,
13
+ from: 'client',
14
+ to: 'SERVER',
15
+ serviceName: 'test',
16
+ procedureName: 'test',
17
+ payload: {
18
+ msg: 'cool',
19
+ },
20
+ });
21
+ const dummyPayloadLarge = () => ({
22
+ id: `${largeId++}`,
23
+ from: 'client',
24
+ to: 'SERVER',
25
+ serviceName: 'test',
26
+ procedureName: 'test',
27
+ payload: largePayload,
28
+ });
29
+ const BENCH_DURATION = 1000;
30
+ describe('transport level bandwidth', async () => {
31
+ const server = http.createServer();
32
+ const port = await onServerReady(server);
33
+ const webSocketServer = await createWebSocketServer(server);
34
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
35
+ bench('send and recv (small payload)', async () => {
36
+ const id = clientTransport.send(dummyPayloadSmall());
37
+ await waitForMessage(serverTransport, (msg) => msg.id === id);
38
+ return;
39
+ }, { time: BENCH_DURATION });
40
+ bench('send and recv (large payload)', async () => {
41
+ const id = clientTransport.send(dummyPayloadLarge());
42
+ await waitForMessage(serverTransport, (msg) => msg.id === id);
43
+ return;
44
+ }, { time: BENCH_DURATION });
45
+ });
46
+ describe('simple router level bandwidth', async () => {
47
+ const httpServer = http.createServer();
48
+ const port = await onServerReady(httpServer);
49
+ const webSocketServer = await createWebSocketServer(httpServer);
50
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
51
+ const serviceDefs = { test: TestServiceConstructor() };
52
+ const server = await createServer(serverTransport, serviceDefs);
53
+ const client = createClient(clientTransport);
54
+ bench('rpc (wait for response)', async () => {
55
+ await client.test.add({ n: 1 });
56
+ }, { time: BENCH_DURATION });
57
+ const [input, output] = await client.test.echo();
58
+ bench('stream (wait for response)', async () => {
59
+ input.push({ msg: 'abc', ignore: false });
60
+ await output.next();
61
+ }, { time: BENCH_DURATION });
62
+ bench('stream', async () => {
63
+ input.push({ msg: 'abc', ignore: false });
64
+ }, { time: BENCH_DURATION });
65
+ });
66
+ describe('complex (50 procedures) router level bandwidth', async () => {
67
+ const httpServer = http.createServer();
68
+ const port = await onServerReady(httpServer);
69
+ const webSocketServer = await createWebSocketServer(httpServer);
70
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
71
+ const serviceDefs = {
72
+ a: StupidlyLargeService(),
73
+ b: StupidlyLargeService(),
74
+ c: StupidlyLargeService(),
75
+ d: StupidlyLargeService(),
76
+ };
77
+ const server = await createServer(serverTransport, serviceDefs);
78
+ const client = createClient(clientTransport);
79
+ bench('rpc (wait for response)', async () => {
80
+ await client.b.f35({ a: 1 });
81
+ }, { time: BENCH_DURATION });
82
+ });
@@ -2,7 +2,7 @@ import http from 'http';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { ServiceBuilder, serializeService } from '../router/builder';
4
4
  import { reply } from '../transport/message';
5
- import { afterAll, beforeAll, describe, expect, test } from 'vitest';
5
+ import { afterAll, describe, expect, test } from 'vitest';
6
6
  import { createWebSocketServer, createWsTransports, onServerReady, } from '../transport/util';
7
7
  import { createServer } from '../router/server';
8
8
  import { createClient } from '../router/client';
@@ -40,6 +40,29 @@ export const TestServiceConstructor = () => ServiceBuilder.create('test')
40
40
  },
41
41
  })
42
42
  .finalize();
43
+ const OrderingServiceConstructor = () => ServiceBuilder.create('test')
44
+ .initialState({
45
+ msgs: [],
46
+ })
47
+ .defineProcedure('add', {
48
+ type: 'rpc',
49
+ input: Type.Object({ n: Type.Number() }),
50
+ output: Type.Object({ ok: Type.Boolean() }),
51
+ async handler(ctx, msg) {
52
+ const { n } = msg.payload;
53
+ ctx.state.msgs.push(n);
54
+ return reply(msg, { ok: true });
55
+ },
56
+ })
57
+ .defineProcedure('getAll', {
58
+ type: 'rpc',
59
+ input: Type.Object({}),
60
+ output: Type.Object({ msgs: Type.Array(Type.Number()) }),
61
+ async handler(ctx, msg) {
62
+ return reply(msg, { msgs: ctx.state.msgs });
63
+ },
64
+ })
65
+ .finalize();
43
66
  test('serialize service to jsonschema', () => {
44
67
  const service = TestServiceConstructor();
45
68
  expect(serializeService(service)).toStrictEqual({
@@ -96,59 +119,76 @@ describe('server-side test', () => {
96
119
  await expect(add({ n: 6 })).resolves.toStrictEqual({ result: 11 });
97
120
  });
98
121
  test('stream basic', async () => {
99
- const [i, o] = asClientStream(initialState, service.procedures.echo);
100
- i.push({ msg: 'abc', ignore: false });
101
- i.push({ msg: 'def', ignore: true });
102
- i.push({ msg: 'ghi', ignore: false });
103
- i.end();
104
- await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
122
+ const [input, output] = asClientStream(initialState, service.procedures.echo);
123
+ input.push({ msg: 'abc', ignore: false });
124
+ input.push({ msg: 'def', ignore: true });
125
+ input.push({ msg: 'ghi', ignore: false });
126
+ input.end();
127
+ await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
105
128
  response: 'abc',
106
129
  });
107
- await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
130
+ await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
108
131
  response: 'ghi',
109
132
  });
110
- expect(o.readableLength).toBe(0);
133
+ expect(output.readableLength).toBe(0);
111
134
  });
112
135
  });
113
- const port = 4445;
114
- describe('client <-> server integration test', () => {
136
+ describe('client <-> server integration test', async () => {
115
137
  const server = http.createServer();
116
- let wss;
117
- beforeAll(async () => {
118
- await onServerReady(server, port);
119
- wss = await createWebSocketServer(server);
120
- });
138
+ const port = await onServerReady(server);
139
+ const webSocketServer = await createWebSocketServer(server);
121
140
  afterAll(() => {
122
- wss.clients.forEach((socket) => {
141
+ webSocketServer.clients.forEach((socket) => {
123
142
  socket.close();
124
143
  });
125
144
  server.close();
126
145
  });
127
146
  test('rpc', async () => {
128
- const [ct, st] = await createWsTransports(port, wss);
147
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
129
148
  const serviceDefs = { test: TestServiceConstructor() };
130
- const server = await createServer(st, serviceDefs);
131
- const client = createClient(ct);
149
+ const server = await createServer(serverTransport, serviceDefs);
150
+ const client = createClient(clientTransport);
132
151
  await expect(client.test.add({ n: 3 })).resolves.toStrictEqual({
133
152
  result: 3,
134
153
  });
135
154
  });
136
155
  test('stream', async () => {
137
- const [ct, st] = await createWsTransports(port, wss);
156
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
138
157
  const serviceDefs = { test: TestServiceConstructor() };
139
- const server = await createServer(st, serviceDefs);
140
- const client = createClient(ct);
141
- const [i, o, close] = await client.test.echo();
142
- i.push({ msg: 'abc', ignore: false });
143
- i.push({ msg: 'def', ignore: true });
144
- i.push({ msg: 'ghi', ignore: false });
145
- i.end();
146
- await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
158
+ const server = await createServer(serverTransport, serviceDefs);
159
+ const client = createClient(clientTransport);
160
+ const [input, output, close] = await client.test.echo();
161
+ input.push({ msg: 'abc', ignore: false });
162
+ input.push({ msg: 'def', ignore: true });
163
+ input.push({ msg: 'ghi', ignore: false });
164
+ input.end();
165
+ await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
147
166
  response: 'abc',
148
167
  });
149
- await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
168
+ await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
150
169
  response: 'ghi',
151
170
  });
152
171
  close();
153
172
  });
173
+ test('message order is preserved in the face of disconnects', async () => {
174
+ const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
175
+ const serviceDefs = { test: OrderingServiceConstructor() };
176
+ const server = await createServer(serverTransport, serviceDefs);
177
+ const client = createClient(clientTransport);
178
+ const expected = [];
179
+ for (let i = 0; i < 50; i++) {
180
+ expected.push(i);
181
+ if (i == 10) {
182
+ clientTransport.ws?.close();
183
+ }
184
+ if (i == 42) {
185
+ clientTransport.ws?.terminate();
186
+ }
187
+ await client.test.add({
188
+ n: i,
189
+ });
190
+ }
191
+ const res = await client.test.getAll({});
192
+ return expect(res.msgs).toStrictEqual(expected);
193
+ });
154
194
  });
@@ -0,0 +1,33 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "type": "articles",
5
+ "id": "1",
6
+ "attributes": {
7
+ "title": "Example article",
8
+ "body": "The shortest article. Ever.",
9
+ "created": "2015-05-22T14:56:29.000Z",
10
+ "updated": "2015-05-22T14:56:28.000Z"
11
+ },
12
+ "relationships": {
13
+ "author": {
14
+ "data": {
15
+ "id": "42",
16
+ "type": "people"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ ],
22
+ "included": [
23
+ {
24
+ "type": "people",
25
+ "id": "42",
26
+ "attributes": {
27
+ "name": "John",
28
+ "age": 21,
29
+ "gender": "male"
30
+ }
31
+ }
32
+ ]
33
+ }