@replit/river 0.8.1 → 0.9.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.
@@ -3,7 +3,7 @@ import { createLocalWebSocketClient, createWebSocketServer, createWsTransports,
3
3
  import { createServer } from '../router/server';
4
4
  import { createClient } from '../router/client';
5
5
  import http from 'http';
6
- import { BinaryFileServiceConstructor, DIV_BY_ZERO, FallibleServiceConstructor, OrderingServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
6
+ import { BinaryFileServiceConstructor, DIV_BY_ZERO, FallibleServiceConstructor, OrderingServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, UploadableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
7
7
  import { UNCAUGHT_ERROR } from '../router/result';
8
8
  import { codecs } from '../codec/codec.test';
9
9
  import { WebSocketClientTransport } from '../transport/impls/ws/client';
@@ -100,6 +100,31 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
100
100
  server,
101
101
  });
102
102
  });
103
+ test('stream with init message', async () => {
104
+ const [clientTransport, serverTransport] = getTransports();
105
+ const serviceDefs = { test: TestServiceConstructor() };
106
+ const server = await createServer(serverTransport, serviceDefs);
107
+ const client = createClient(clientTransport);
108
+ const [input, output, close] = await client.test.echoWithPrefix.stream({
109
+ prefix: 'test',
110
+ });
111
+ input.push({ msg: 'abc', ignore: false });
112
+ input.push({ msg: 'def', ignore: true });
113
+ input.push({ msg: 'ghi', ignore: false });
114
+ input.end();
115
+ const result1 = await iterNext(output);
116
+ assert(result1.ok);
117
+ expect(result1.payload).toStrictEqual({ response: 'test abc' });
118
+ const result2 = await iterNext(output);
119
+ assert(result2.ok);
120
+ expect(result2.payload).toStrictEqual({ response: 'test ghi' });
121
+ close();
122
+ await testFinishesCleanly({
123
+ clientTransports: [clientTransport],
124
+ serverTransport,
125
+ server,
126
+ });
127
+ });
103
128
  test('fallible stream', async () => {
104
129
  const [clientTransport, serverTransport] = getTransports();
105
130
  const serviceDefs = { test: FallibleServiceConstructor() };
@@ -169,6 +194,44 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
169
194
  server,
170
195
  });
171
196
  });
197
+ test('upload', async () => {
198
+ const [clientTransport, serverTransport] = getTransports();
199
+ const serviceDefs = { uploadable: UploadableServiceConstructor() };
200
+ const server = await createServer(serverTransport, serviceDefs);
201
+ const client = createClient(clientTransport);
202
+ const [addStream, addResult] = await client.uploadable.addMultiple.upload();
203
+ addStream.push({ n: 1 });
204
+ addStream.push({ n: 2 });
205
+ addStream.end();
206
+ const result = await addResult;
207
+ assert(result.ok);
208
+ expect(result.payload).toStrictEqual({ result: 3 });
209
+ await testFinishesCleanly({
210
+ clientTransports: [clientTransport],
211
+ serverTransport,
212
+ server,
213
+ });
214
+ });
215
+ test('upload with init message', async () => {
216
+ const [clientTransport, serverTransport] = getTransports();
217
+ const serviceDefs = { uploadable: UploadableServiceConstructor() };
218
+ const server = await createServer(serverTransport, serviceDefs);
219
+ const client = createClient(clientTransport);
220
+ const [addStream, addResult] = await client.uploadable.addMultipleWithPrefix.upload({
221
+ prefix: 'test',
222
+ });
223
+ addStream.push({ n: 1 });
224
+ addStream.push({ n: 2 });
225
+ addStream.end();
226
+ const result = await addResult;
227
+ assert(result.ok);
228
+ expect(result.payload).toStrictEqual({ result: 'test 3' });
229
+ await testFinishesCleanly({
230
+ clientTransports: [clientTransport],
231
+ serverTransport,
232
+ server,
233
+ });
234
+ });
172
235
  test('message order is preserved in the face of disconnects', async () => {
173
236
  const [clientTransport, serverTransport] = getTransports();
174
237
  const serviceDefs = { test: OrderingServiceConstructor() };
@@ -6,7 +6,7 @@ const waitUntilOptions = {
6
6
  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
- expect(t.messageHandlers, `transport ${t.clientId} should not have open message handlers after the test`).toStrictEqual(new Set());
9
+ expect(t.eventDispatcher.numberOfListeners('message'), `transport ${t.clientId} should not have open message handlers after the test`).equal(0);
10
10
  }
11
11
  export async function waitUntil(valueGetter, expected, message) {
12
12
  return vi
@@ -52,6 +52,33 @@ export declare const TestServiceConstructor: () => {
52
52
  }, never>>, void, unknown>) => Promise<void>;
53
53
  type: "stream";
54
54
  };
55
+ } & {
56
+ echoWithPrefix: {
57
+ init: import("@sinclair/typebox").TObject<{
58
+ prefix: import("@sinclair/typebox").TString;
59
+ }>;
60
+ input: import("@sinclair/typebox").TObject<{
61
+ msg: import("@sinclair/typebox").TString;
62
+ ignore: import("@sinclair/typebox").TBoolean;
63
+ end: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
64
+ }>;
65
+ output: import("@sinclair/typebox").TObject<{
66
+ response: import("@sinclair/typebox").TString;
67
+ }>;
68
+ errors: import("@sinclair/typebox").TNever;
69
+ handler: (context: import("../../router").ServiceContextWithState<{
70
+ count: number;
71
+ }>, init: import("../../transport/message").TransportMessage<{
72
+ prefix: string;
73
+ }>, input: AsyncIterable<import("../../transport/message").TransportMessage<{
74
+ end?: boolean | undefined;
75
+ msg: string;
76
+ ignore: boolean;
77
+ }>>, output: import("it-pushable").Pushable<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
78
+ response: string;
79
+ }, never>>, void, unknown>) => Promise<void>;
80
+ type: "stream";
81
+ };
55
82
  };
56
83
  };
57
84
  export declare const OrderingServiceConstructor: () => {
@@ -216,4 +243,46 @@ export declare const SubscribableServiceConstructor: () => {
216
243
  };
217
244
  };
218
245
  };
246
+ export declare const UploadableServiceConstructor: () => {
247
+ name: "uploadable";
248
+ state: {};
249
+ procedures: {
250
+ addMultiple: {
251
+ input: import("@sinclair/typebox").TObject<{
252
+ n: import("@sinclair/typebox").TNumber;
253
+ }>;
254
+ output: import("@sinclair/typebox").TObject<{
255
+ result: import("@sinclair/typebox").TNumber;
256
+ }>;
257
+ errors: import("@sinclair/typebox").TNever;
258
+ handler: (context: import("../../router").ServiceContextWithState<{}>, input: AsyncIterable<import("../../transport/message").TransportMessage<{
259
+ n: number;
260
+ }>>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
261
+ result: number;
262
+ }, never>>>;
263
+ type: "upload";
264
+ };
265
+ } & {
266
+ addMultipleWithPrefix: {
267
+ init: import("@sinclair/typebox").TObject<{
268
+ prefix: import("@sinclair/typebox").TString;
269
+ }>;
270
+ input: import("@sinclair/typebox").TObject<{
271
+ n: import("@sinclair/typebox").TNumber;
272
+ }>;
273
+ output: import("@sinclair/typebox").TObject<{
274
+ result: import("@sinclair/typebox").TString;
275
+ }>;
276
+ errors: import("@sinclair/typebox").TNever;
277
+ handler: (context: import("../../router").ServiceContextWithState<{}>, init: import("../../transport/message").TransportMessage<{
278
+ prefix: string;
279
+ }>, input: AsyncIterable<import("../../transport/message").TransportMessage<{
280
+ n: number;
281
+ }>>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
282
+ result: string;
283
+ }, never>>>;
284
+ type: "upload";
285
+ };
286
+ };
287
+ };
219
288
  //# sourceMappingURL=services.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/services.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,eAAO,MAAM,WAAW;;;;EAItB,CAAC;AACH,eAAO,MAAM,YAAY;;EAA2C,CAAC;AAErE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCpB,CAAC;AAEhB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyBxB,CAAC;AAEhB,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;CAc1B,CAAC;AAEhB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiExB,CAAC;AAEhB,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B5B,CAAC"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/services.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,eAAO,MAAM,WAAW;;;;EAItB,CAAC;AACH,eAAO,MAAM,YAAY;;EAA2C,CAAC;AAErE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDpB,CAAC;AAEhB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyBxB,CAAC;AAEhB,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;CAc1B,CAAC;AAEhB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiExB,CAAC;AAEhB,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B5B,CAAC;AAEhB,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC1B,CAAC"}
@@ -40,6 +40,21 @@ export const TestServiceConstructor = () => ServiceBuilder.create('test')
40
40
  }
41
41
  }
42
42
  },
43
+ })
44
+ .defineProcedure('echoWithPrefix', {
45
+ type: 'stream',
46
+ init: Type.Object({ prefix: Type.String() }),
47
+ input: EchoRequest,
48
+ output: EchoResponse,
49
+ errors: Type.Never(),
50
+ async handler(_ctx, init, msgStream, returnStream) {
51
+ for await (const msg of msgStream) {
52
+ const req = msg.payload;
53
+ if (!req.ignore) {
54
+ returnStream.push(reply(msg, Ok({ response: `${init.payload.prefix} ${req.msg}` })));
55
+ }
56
+ }
57
+ },
43
58
  })
44
59
  .finalize();
45
60
  export const OrderingServiceConstructor = () => ServiceBuilder.create('test')
@@ -171,3 +186,39 @@ export const SubscribableServiceConstructor = () => ServiceBuilder.create('subsc
171
186
  },
172
187
  })
173
188
  .finalize();
189
+ export const UploadableServiceConstructor = () => ServiceBuilder.create('uploadable')
190
+ .initialState({})
191
+ .defineProcedure('addMultiple', {
192
+ type: 'upload',
193
+ input: Type.Object({ n: Type.Number() }),
194
+ output: Type.Object({ result: Type.Number() }),
195
+ errors: Type.Never(),
196
+ async handler(_ctx, msgStream) {
197
+ let result = 0;
198
+ let lastMsg;
199
+ for await (const msg of msgStream) {
200
+ const { n } = msg.payload;
201
+ result += n;
202
+ lastMsg = msg;
203
+ }
204
+ return reply(lastMsg, Ok({ result: result }));
205
+ },
206
+ })
207
+ .defineProcedure('addMultipleWithPrefix', {
208
+ type: 'upload',
209
+ init: Type.Object({ prefix: Type.String() }),
210
+ input: Type.Object({ n: Type.Number() }),
211
+ output: Type.Object({ result: Type.String() }),
212
+ errors: Type.Never(),
213
+ async handler(_ctx, init, msgStream) {
214
+ let result = 0;
215
+ let lastMsg;
216
+ for await (const msg of msgStream) {
217
+ const { n } = msg.payload;
218
+ result += n;
219
+ lastMsg = msg;
220
+ }
221
+ return reply(lastMsg, Ok({ result: init.payload.prefix + ' ' + result }));
222
+ },
223
+ })
224
+ .finalize();
@@ -1,6 +1,6 @@
1
- import { asClientRpc, asClientStream, asClientSubscription, iterNext, } from '../util/testHelpers';
1
+ import { asClientRpc, asClientStream, asClientStreamWithInitialization, asClientSubscription, asClientUpload, asClientUploadWithInitialization, iterNext, } from '../util/testHelpers';
2
2
  import { assert, describe, expect, test } from 'vitest';
3
- import { DIV_BY_ZERO, FallibleServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
3
+ import { DIV_BY_ZERO, FallibleServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, UploadableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
4
4
  import { UNCAUGHT_ERROR } from '../router/result';
5
5
  import { Observable } from './fixtures/observable';
6
6
  describe('server-side test', () => {
@@ -48,6 +48,20 @@ describe('server-side test', () => {
48
48
  expect(result2.payload).toStrictEqual({ response: 'ghi' });
49
49
  expect(output.readableLength).toBe(0);
50
50
  });
51
+ test('stream with initialization', async () => {
52
+ const [input, output] = asClientStreamWithInitialization(initialState, service.procedures.echoWithPrefix, { prefix: 'test' });
53
+ input.push({ msg: 'abc', ignore: false });
54
+ input.push({ msg: 'def', ignore: true });
55
+ input.push({ msg: 'ghi', ignore: false });
56
+ input.end();
57
+ const result1 = await iterNext(output);
58
+ assert(result1 && result1.ok);
59
+ expect(result1.payload).toStrictEqual({ response: 'test abc' });
60
+ const result2 = await iterNext(output);
61
+ assert(result2 && result2.ok);
62
+ expect(result2.payload).toStrictEqual({ response: 'test ghi' });
63
+ expect(output.readableLength).toBe(0);
64
+ });
51
65
  test('fallible stream', async () => {
52
66
  const service = FallibleServiceConstructor();
53
67
  const [input, output] = asClientStream({}, service.procedures.echo);
@@ -85,4 +99,23 @@ describe('server-side test', () => {
85
99
  assert(streamResult2 && streamResult1.ok);
86
100
  expect(streamResult2.payload).toStrictEqual({ result: 3 });
87
101
  });
102
+ test('uploads', async () => {
103
+ const service = UploadableServiceConstructor();
104
+ const [input, result] = asClientUpload({}, service.procedures.addMultiple);
105
+ input.push({ n: 1 });
106
+ input.push({ n: 2 });
107
+ input.end();
108
+ expect(await result).toStrictEqual({ ok: true, payload: { result: 3 } });
109
+ });
110
+ test('uploads with initialization', async () => {
111
+ const service = UploadableServiceConstructor();
112
+ const [input, result] = asClientUploadWithInitialization({}, service.procedures.addMultipleWithPrefix, { prefix: 'test' });
113
+ input.push({ n: 1 });
114
+ input.push({ n: 2 });
115
+ input.end();
116
+ expect(await result).toStrictEqual({
117
+ ok: true,
118
+ payload: { result: 'test 3' },
119
+ });
120
+ });
88
121
  });
@@ -52,14 +52,14 @@ describe('procedures should leave no trace after finishing', async () => {
52
52
  const serviceDefs = { test: TestServiceConstructor() };
53
53
  const server = await createServer(serverTransport, serviceDefs);
54
54
  const client = createClient(clientTransport);
55
- let serverListeners = serverTransport.messageHandlers.size;
56
- let clientListeners = clientTransport.messageHandlers.size;
55
+ let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
56
+ let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
57
57
  // start procedure
58
58
  await client.test.add.rpc({ n: 3 });
59
59
  // end procedure
60
60
  // number of message handlers shouldn't increase after rpc
61
- expect(serverTransport.messageHandlers.size).toEqual(serverListeners);
62
- expect(clientTransport.messageHandlers.size).toEqual(clientListeners);
61
+ expect(serverTransport.eventDispatcher.numberOfListeners('message')).toEqual(serverListeners);
62
+ expect(clientTransport.eventDispatcher.numberOfListeners('message')).toEqual(clientListeners);
63
63
  // check number of connections
64
64
  expect(serverTransport.connections.size).toEqual(1);
65
65
  expect(clientTransport.connections.size).toEqual(1);
@@ -73,8 +73,8 @@ describe('procedures should leave no trace after finishing', async () => {
73
73
  const serviceDefs = { test: TestServiceConstructor() };
74
74
  const server = await createServer(serverTransport, serviceDefs);
75
75
  const client = createClient(clientTransport);
76
- let serverListeners = serverTransport.messageHandlers.size;
77
- let clientListeners = clientTransport.messageHandlers.size;
76
+ let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
77
+ let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
78
78
  // start procedure
79
79
  const [input, output, close] = await client.test.echo.stream();
80
80
  input.push({ msg: '1', ignore: false });
@@ -93,8 +93,8 @@ describe('procedures should leave no trace after finishing', async () => {
93
93
  close();
94
94
  // end procedure
95
95
  // number of message handlers shouldn't increase after stream ends
96
- expect(serverTransport.messageHandlers.size).toEqual(serverListeners);
97
- expect(clientTransport.messageHandlers.size).toEqual(clientListeners);
96
+ expect(serverTransport.eventDispatcher.numberOfListeners('message')).toEqual(serverListeners);
97
+ expect(clientTransport.eventDispatcher.numberOfListeners('message')).toEqual(clientListeners);
98
98
  // check number of connections
99
99
  expect(serverTransport.connections.size).toEqual(1);
100
100
  expect(clientTransport.connections.size).toEqual(1);
@@ -108,8 +108,8 @@ describe('procedures should leave no trace after finishing', async () => {
108
108
  const serviceDefs = { test: SubscribableServiceConstructor() };
109
109
  const server = await createServer(serverTransport, serviceDefs);
110
110
  const client = createClient(clientTransport);
111
- let serverListeners = serverTransport.messageHandlers.size;
112
- let clientListeners = clientTransport.messageHandlers.size;
111
+ let serverListeners = serverTransport.eventDispatcher.numberOfListeners('message');
112
+ let clientListeners = clientTransport.eventDispatcher.numberOfListeners('message');
113
113
  // start procedure
114
114
  const [subscription, close] = await client.test.value.subscribe({});
115
115
  let result = await iterNext(subscription);
@@ -123,8 +123,8 @@ describe('procedures should leave no trace after finishing', async () => {
123
123
  close();
124
124
  // end procedure
125
125
  // number of message handlers shouldn't increase after stream ends
126
- expect(serverTransport.messageHandlers.size).toEqual(serverListeners);
127
- expect(clientTransport.messageHandlers.size).toEqual(clientListeners);
126
+ expect(serverTransport.eventDispatcher.numberOfListeners('message')).toEqual(serverListeners);
127
+ expect(clientTransport.eventDispatcher.numberOfListeners('message')).toEqual(clientListeners);
128
128
  // check number of connections
129
129
  expect(serverTransport.connections.size).toEqual(1);
130
130
  expect(clientTransport.connections.size).toEqual(1);
@@ -46,6 +46,45 @@ describe('serialize service to jsonschema', () => {
46
46
  errors: { not: {} },
47
47
  type: 'stream',
48
48
  },
49
+ echoWithPrefix: {
50
+ errors: {
51
+ not: {},
52
+ },
53
+ init: {
54
+ properties: {
55
+ prefix: {
56
+ type: 'string',
57
+ },
58
+ },
59
+ required: ['prefix'],
60
+ type: 'object',
61
+ },
62
+ input: {
63
+ properties: {
64
+ end: {
65
+ type: 'boolean',
66
+ },
67
+ ignore: {
68
+ type: 'boolean',
69
+ },
70
+ msg: {
71
+ type: 'string',
72
+ },
73
+ },
74
+ required: ['msg', 'ignore'],
75
+ type: 'object',
76
+ },
77
+ output: {
78
+ properties: {
79
+ response: {
80
+ type: 'string',
81
+ },
82
+ },
83
+ required: ['response'],
84
+ type: 'object',
85
+ },
86
+ type: 'stream',
87
+ },
49
88
  },
50
89
  });
51
90
  });