@replit/river 0.6.4 → 0.7.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.
Files changed (41) hide show
  1. package/dist/__tests__/e2e.test.js +7 -8
  2. package/dist/__tests__/typescript-stress.test.d.ts +4 -2
  3. package/dist/__tests__/typescript-stress.test.d.ts.map +1 -1
  4. package/dist/__tests__/typescript-stress.test.js +3 -1
  5. package/dist/router/client.d.ts +3 -2
  6. package/dist/router/client.d.ts.map +1 -1
  7. package/dist/router/client.js +5 -5
  8. package/dist/router/server.d.ts +2 -2
  9. package/dist/router/server.d.ts.map +1 -1
  10. package/dist/router/server.js +10 -10
  11. package/dist/testUtils.d.ts +6 -5
  12. package/dist/testUtils.d.ts.map +1 -1
  13. package/dist/testUtils.js +7 -16
  14. package/dist/transport/impls/stdio/stdio.d.ts +37 -0
  15. package/dist/transport/impls/stdio/stdio.d.ts.map +1 -0
  16. package/dist/transport/impls/stdio/stdio.js +80 -0
  17. package/dist/transport/impls/stdio/stdio.test.d.ts +2 -0
  18. package/dist/transport/impls/stdio/stdio.test.d.ts.map +1 -0
  19. package/dist/transport/impls/stdio/stdio.test.js +20 -0
  20. package/dist/transport/impls/ws/client.d.ts +45 -0
  21. package/dist/transport/impls/ws/client.d.ts.map +1 -0
  22. package/dist/transport/impls/ws/client.js +102 -0
  23. package/dist/transport/impls/ws/connection.d.ts +11 -0
  24. package/dist/transport/impls/ws/connection.d.ts.map +1 -0
  25. package/dist/transport/impls/ws/connection.js +22 -0
  26. package/dist/transport/impls/ws/server.d.ts +19 -0
  27. package/dist/transport/impls/ws/server.d.ts.map +1 -0
  28. package/dist/transport/impls/ws/server.js +53 -0
  29. package/dist/transport/impls/ws/ws.test.d.ts +2 -0
  30. package/dist/transport/impls/ws/ws.test.d.ts.map +1 -0
  31. package/dist/transport/impls/ws/ws.test.js +97 -0
  32. package/dist/transport/index.d.ts +3 -3
  33. package/dist/transport/index.d.ts.map +1 -1
  34. package/dist/transport/index.js +6 -3
  35. package/dist/transport/message.d.ts +4 -1
  36. package/dist/transport/message.d.ts.map +1 -1
  37. package/dist/transport/message.js +3 -0
  38. package/dist/transport/transport.d.ts +132 -0
  39. package/dist/transport/transport.d.ts.map +1 -0
  40. package/dist/transport/transport.js +241 -0
  41. package/package.json +3 -2
@@ -7,15 +7,15 @@ import { BinaryFileServiceConstructor, DIV_BY_ZERO, FallibleServiceConstructor,
7
7
  import { UNCAUGHT_ERROR } from '../router/result';
8
8
  import { codecs } from '../codec/codec.test';
9
9
  describe.each(codecs)('client <-> server integration test ($name codec)', async ({ codec }) => {
10
- const server = http.createServer();
11
- const port = await onServerReady(server);
12
- const webSocketServer = await createWebSocketServer(server);
10
+ const httpServer = http.createServer();
11
+ const port = await onServerReady(httpServer);
12
+ const webSocketServer = await createWebSocketServer(httpServer);
13
13
  const getTransports = () => createWsTransports(port, webSocketServer, codec);
14
14
  afterAll(() => {
15
15
  webSocketServer.clients.forEach((socket) => {
16
16
  socket.close();
17
17
  });
18
- server.close();
18
+ httpServer.close();
19
19
  });
20
20
  test('rpc', async () => {
21
21
  const [clientTransport, serverTransport] = getTransports();
@@ -104,10 +104,10 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
104
104
  for (let i = 0; i < 50; i++) {
105
105
  expected.push(i);
106
106
  if (i == 10) {
107
- clientTransport.ws?.close();
107
+ clientTransport.connections.forEach((conn) => conn.ws.close());
108
108
  }
109
109
  if (i == 42) {
110
- clientTransport.ws?.terminate();
110
+ clientTransport.connections.forEach((conn) => conn.ws.terminate());
111
111
  }
112
112
  await client.test.add({
113
113
  n: i,
@@ -157,8 +157,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
157
157
  }
158
158
  // cleanup
159
159
  for (let i = 0; i < CONCURRENCY; i++) {
160
- const [input, _output, close] = openStreams[i];
161
- input.end();
160
+ const [_input, _output, close] = openStreams[i];
162
161
  close();
163
162
  }
164
163
  });
@@ -1,5 +1,5 @@
1
1
  import { MessageId, OpaqueTransportMessage } from '../transport/message';
2
- import { Transport } from '../transport/types';
2
+ import { Connection, Transport } from '../transport/transport';
3
3
  export declare const StupidlyLargeService: () => {
4
4
  name: "test";
5
5
  state: {};
@@ -1377,9 +1377,11 @@ export declare const StupidlyLargeService: () => {
1377
1377
  };
1378
1378
  };
1379
1379
  };
1380
- export declare class MockTransport extends Transport {
1380
+ export declare class MockTransport extends Transport<Connection> {
1381
1381
  constructor(clientId: string);
1382
1382
  send(msg: OpaqueTransportMessage): MessageId;
1383
+ setupConnectionStatusListeners(): void;
1384
+ createNewConnection(): Promise<void>;
1383
1385
  close(): Promise<void>;
1384
1386
  }
1385
1387
  //# sourceMappingURL=typescript-stress.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"typescript-stress.test.d.ts","sourceRoot":"","sources":["../../__tests__/typescript-stress.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAS,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA8B/C,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDlB,CAAC;AAGhB,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,QAAQ,EAAE,MAAM;IAI5B,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IAKtC,KAAK;CACZ"}
1
+ {"version":3,"file":"typescript-stress.test.d.ts","sourceRoot":"","sources":["../../__tests__/typescript-stress.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAS,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA8B/D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDlB,CAAC;AAGhB,qBAAa,aAAc,SAAQ,SAAS,CAAC,UAAU,CAAC;gBAC1C,QAAQ,EAAE,MAAM;IAI5B,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IAK5C,8BAA8B;IACxB,mBAAmB;IACnB,KAAK;CACZ"}
@@ -3,7 +3,7 @@ import { ServiceBuilder, serializeService } from '../router/builder';
3
3
  import { Type } from '@sinclair/typebox';
4
4
  import { reply } from '../transport/message';
5
5
  import { createServer } from '../router/server';
6
- import { Transport } from '../transport/types';
6
+ import { Transport } from '../transport/transport';
7
7
  import { NaiveJsonCodec } from '../codec/json';
8
8
  import { createClient } from '../router/client';
9
9
  import { Ok } from '../router/result';
@@ -90,6 +90,8 @@ export class MockTransport extends Transport {
90
90
  const id = msg.id;
91
91
  return id;
92
92
  }
93
+ setupConnectionStatusListeners() { }
94
+ async createNewConnection() { }
93
95
  async close() { }
94
96
  }
95
97
  describe("ensure typescript doesn't give up trying to infer the types for large services", () => {
@@ -1,7 +1,8 @@
1
- import { Transport } from '../transport/types';
1
+ import { Connection, Transport } from '../transport/transport';
2
2
  import { AnyService, ProcErrors, ProcInput, ProcOutput, ProcType } from './builder';
3
3
  import type { Pushable } from 'it-pushable';
4
4
  import { Server } from './server';
5
+ import { TransportClientId } from '../transport/message';
5
6
  import { Static } from '@sinclair/typebox';
6
7
  import { Result } from './result';
7
8
  type AsyncIter<T> = AsyncGenerator<T, T, unknown>;
@@ -37,6 +38,6 @@ export type ServerClient<Srv extends Server<Record<string, AnyService>>> = {
37
38
  * @param {Transport} transport - The transport to use for communication.
38
39
  * @returns The client for the server.
39
40
  */
40
- export declare const createClient: <Srv extends Server<Record<string, AnyService>>>(transport: Transport) => ServerClient<Srv>;
41
+ export declare const createClient: <Srv extends Server<Record<string, AnyService>>>(transport: Transport<Connection>, serverId?: TransportClientId) => ServerClient<Srv>;
41
42
  export {};
42
43
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../router/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACT,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAMlC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,SAAS,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAElD;;;;GAIG;AACH,KAAK,aAAa,CAAC,MAAM,SAAS,UAAU,IAAI;KAC7C,QAAQ,IAAI,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,QAAQ,CAChD,MAAM,EACN,QAAQ,CACT,SAAS,KAAK,GAEX,CACE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,KACvC,OAAO,CACV,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF,GAED,MAAM,OAAO,CACX;QACE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7C,SAAS,CACP,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF;QACD,MAAM,IAAI;KACX,CACF;CACN,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI;KACxE,OAAO,IAAI,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;CAC5E,CAAC;AAgCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,8DACZ,SAAS,sBAgFO,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../router/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACT,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAIL,iBAAiB,EAElB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,SAAS,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAElD;;;;GAIG;AACH,KAAK,aAAa,CAAC,MAAM,SAAS,UAAU,IAAI;KAC7C,QAAQ,IAAI,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,QAAQ,CAChD,MAAM,EACN,QAAQ,CACT,SAAS,KAAK,GAEX,CACE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,KACvC,OAAO,CACV,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF,GAED,MAAM,OAAO,CACX;QACE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7C,SAAS,CACP,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF;QACD,MAAM,IAAI;KACX,CACF;CACN,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI;KACxE,OAAO,IAAI,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;CAC5E,CAAC;AAgCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,8DACZ,UAAU,UAAU,CAAC,aACtB,iBAAiB,sBAkFA,CAAC"}
@@ -34,14 +34,14 @@ function _createRecursiveProxy(callback, path) {
34
34
  * @param {Transport} transport - The transport to use for communication.
35
35
  * @returns The client for the server.
36
36
  */
37
- export const createClient = (transport) => _createRecursiveProxy(async (opts) => {
37
+ export const createClient = (transport, serverId = 'SERVER') => _createRecursiveProxy(async (opts) => {
38
38
  const [serviceName, procName] = [...opts.path];
39
39
  const [input] = opts.args;
40
40
  const streamId = nanoid();
41
41
  function belongsToSameStream(msg) {
42
42
  return (msg.serviceName === serviceName &&
43
43
  msg.procedureName === procName &&
44
- (msg.streamId === streamId || msg.streamId === 'global'));
44
+ msg.streamId === streamId);
45
45
  }
46
46
  if (input === undefined) {
47
47
  // stream case (stream methods are called with zero arguments)
@@ -51,7 +51,7 @@ export const createClient = (transport) => _createRecursiveProxy(async (opts) =>
51
51
  // this gets cleaned up on i.end() which is called by closeHandler
52
52
  (async () => {
53
53
  for await (const rawIn of inputStream) {
54
- const m = msg(transport.clientId, 'SERVER', serviceName, procName, streamId, rawIn);
54
+ const m = msg(transport.clientId, serverId, serviceName, procName, streamId, rawIn);
55
55
  m.controlFlags |= 2 /* ControlFlags.StreamOpenBit */;
56
56
  transport.send(m);
57
57
  }
@@ -66,7 +66,7 @@ export const createClient = (transport) => _createRecursiveProxy(async (opts) =>
66
66
  const closeHandler = () => {
67
67
  inputStream.end();
68
68
  outputStream.end();
69
- const closeMessage = msg(transport.clientId, 'SERVER', serviceName, procName, streamId, {});
69
+ const closeMessage = msg(transport.clientId, serverId, serviceName, procName, streamId, { type: 'CLOSE' });
70
70
  closeMessage.controlFlags |= 4 /* ControlFlags.StreamClosedBit */;
71
71
  transport.send(closeMessage);
72
72
  transport.removeMessageListener(listener);
@@ -75,7 +75,7 @@ export const createClient = (transport) => _createRecursiveProxy(async (opts) =>
75
75
  }
76
76
  else {
77
77
  // rpc case
78
- const m = msg(transport.clientId, 'SERVER', serviceName, procName, streamId, input);
78
+ const m = msg(transport.clientId, serverId, serviceName, procName, streamId, input);
79
79
  // rpc is a stream open + close
80
80
  m.controlFlags |=
81
81
  2 /* ControlFlags.StreamOpenBit */ | 4 /* ControlFlags.StreamClosedBit */;
@@ -1,4 +1,4 @@
1
- import { Transport } from '../transport/types';
1
+ import { Connection, Transport } from '../transport/transport';
2
2
  import { AnyService } from './builder';
3
3
  import { ServiceContext } from './context';
4
4
  /**
@@ -17,5 +17,5 @@ export interface Server<Services> {
17
17
  * @param extendedContext - An optional object containing additional context to be passed to all services.
18
18
  * @returns A promise that resolves to a server instance with the registered services.
19
19
  */
20
- export declare function createServer<Services extends Record<string, AnyService>>(transport: Transport, services: Services, extendedContext?: Omit<ServiceContext, 'state'>): Promise<Server<Services>>;
20
+ export declare function createServer<Services extends Record<string, AnyService>>(transport: Transport<Connection>, services: Services, extendedContext?: Omit<ServiceContext, 'state'>): Promise<Server<Services>>;
21
21
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../router/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAgB,UAAU,EAAE,MAAM,WAAW,CAAC;AAUrD,OAAO,EAAE,cAAc,EAA2B,MAAM,WAAW,CAAC;AAWpE;;;GAGG;AACH,MAAM,WAAW,MAAM,CAAC,QAAQ;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAWD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAC5E,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,QAAQ,EAClB,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAoJ3B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../router/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAgB,UAAU,EAAE,MAAM,WAAW,CAAC;AAWrD,OAAO,EAAE,cAAc,EAA2B,MAAM,WAAW,CAAC;AAWpE;;;GAGG;AACH,MAAM,WAAW,MAAM,CAAC,QAAQ;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAWD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAC5E,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,QAAQ,EAAE,QAAQ,EAClB,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAqJ3B"}
@@ -1,5 +1,5 @@
1
1
  import { pushable } from 'it-pushable';
2
- import { isStreamClose, isStreamOpen, reply, } from '../transport/message';
2
+ import { ControlMessagePayloadSchema, isStreamClose, isStreamOpen, reply, } from '../transport/message';
3
3
  import { log } from '../logging';
4
4
  import { Value } from '@sinclair/typebox/value';
5
5
  import { Err, UNCAUGHT_ERROR, } from './result';
@@ -29,7 +29,7 @@ export async function createServer(transport, services, extendedContext) {
29
29
  contextMap.set(service, { ...extendedContext, state: service.state });
30
30
  }
31
31
  const handler = async (msg) => {
32
- if (msg.to !== 'SERVER') {
32
+ if (msg.to !== transport.clientId) {
33
33
  log?.info(`${transport.clientId} -- got msg with destination that isn't the server, ignoring`);
34
34
  return;
35
35
  }
@@ -44,12 +44,7 @@ export async function createServer(transport, services, extendedContext) {
44
44
  return;
45
45
  }
46
46
  const procedure = service.procedures[msg.procedureName];
47
- if (!Value.Check(procedure.input, msg.payload)) {
48
- log?.error(`${transport.clientId} -- procedure ${msg.serviceName}.${msg.procedureName} received invalid payload: ${msg.payload}`);
49
- return;
50
- }
51
- const inputMessage = msg;
52
- if (isStreamOpen(inputMessage.controlFlags)) {
47
+ if (isStreamOpen(msg.controlFlags)) {
53
48
  const incoming = pushable({ objectMode: true });
54
49
  const outgoing = pushable({ objectMode: true });
55
50
  const openPromises = [
@@ -98,8 +93,13 @@ export async function createServer(transport, services, extendedContext) {
98
93
  log?.warn(`${transport.clientId} -- couldn't find a matching procedure stream for ${msg.serviceName}.${msg.procedureName}:${msg.streamId}`);
99
94
  return;
100
95
  }
101
- procStream.incoming.push(inputMessage);
102
- if (isStreamClose(inputMessage.controlFlags)) {
96
+ if (Value.Check(procedure.input, msg.payload)) {
97
+ procStream.incoming.push(msg);
98
+ }
99
+ else if (!Value.Check(ControlMessagePayloadSchema, msg.payload)) {
100
+ log?.error(`${transport.clientId} -- procedure ${msg.serviceName}.${msg.procedureName} received invalid payload: ${JSON.stringify(msg.payload)}`);
101
+ }
102
+ if (isStreamClose(msg.controlFlags)) {
103
103
  procStream.incoming.end();
104
104
  await Promise.all(procStream.openPromises);
105
105
  procStream.outgoing.end();
@@ -2,13 +2,14 @@
2
2
  import WebSocket from 'isomorphic-ws';
3
3
  import { WebSocketServer } from 'ws';
4
4
  import http from 'http';
5
- import { WebSocketTransport } from './transport/impls/ws';
5
+ import { WebSocketClientTransport } from './transport/impls/ws/client';
6
6
  import { Static, TObject } from '@sinclair/typebox';
7
7
  import { Procedure, ServiceContext } from './router';
8
- import { OpaqueTransportMessage, TransportMessage } from './transport';
8
+ import { OpaqueTransportMessage, TransportClientId, TransportMessage } from './transport';
9
9
  import { Pushable } from 'it-pushable';
10
10
  import { Result, RiverError, RiverUncaughtSchema } from './router/result';
11
11
  import { Codec } from './codec';
12
+ import { WebSocketServerTransport } from './transport/impls/ws/server';
12
13
  /**
13
14
  * Creates a WebSocket server instance using the provided HTTP server.
14
15
  * Only used as helper for testing.
@@ -35,9 +36,9 @@ export declare function createLocalWebSocketClient(port: number): Promise<WebSoc
35
36
  * Creates a pair of WebSocket transports for testing purposes.
36
37
  * @param port - The port number to use for the client transport. This should be acquired after starting a server via {@link createWebSocketServer}.
37
38
  * @param wss - The WebSocketServer instance to use for the server transport.
38
- * @returns An array containing the client and server {@link WebSocketTransport} instances.
39
+ * @returns An array containing the client and server {@link WebSocketClientTransport} instances.
39
40
  */
40
- export declare function createWsTransports(port: number, wss: WebSocketServer, codec?: Codec): [WebSocketTransport, WebSocketTransport];
41
+ export declare function createWsTransports(port: number, wss: WebSocketServer, codec?: Codec): [WebSocketClientTransport, WebSocketServerTransport];
41
42
  /**
42
43
  * Transforms an RPC procedure definition into a normal function call.
43
44
  * This should only be used for testing.
@@ -73,7 +74,7 @@ export declare function asClientStream<State extends object | unknown, I extends
73
74
  * @param streamId - The optional stream ID.
74
75
  * @returns The transport message.
75
76
  */
76
- export declare function payloadToTransportMessage<Payload extends object>(payload: Payload, streamId?: string): TransportMessage<Payload>;
77
+ export declare function payloadToTransportMessage<Payload extends object>(payload: Payload, streamId?: string, from?: TransportClientId, to?: TransportClientId): TransportMessage<Payload>;
77
78
  /**
78
79
  * Creates a dummy opaque transport message for testing purposes.
79
80
  * @returns The created opaque transport message.
@@ -1 +1 @@
1
- {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../testUtils.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAGjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAY,MAAM,aAAa,CAAC;AACjD,OAAO,EAEL,MAAM,EACN,UAAU,EACV,mBAAmB,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,4EAE9D;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,sBAI5D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,KAAK,CAAC,EAAE,KAAK,GACZ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAwB1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACtC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,SAGxC,OAAO,CAAC,CAAC,KACb,QACD,OAAO,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,0BAA0B,CAAC,CAAC,CAClE,CAYF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C;IACD,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC;CAC5E,CAuDA;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,SAAS,MAAM,EAC9D,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,MAAM,6BAUlB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,sBAAsB,CAKpE"}
1
+ {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../testUtils.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAY,MAAM,aAAa,CAAC;AACjD,OAAO,EAEL,MAAM,EACN,UAAU,EACV,mBAAmB,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEvE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,4EAE9D;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,sBAI5D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,KAAK,CAAC,EAAE,KAAK,GACZ,CAAC,wBAAwB,EAAE,wBAAwB,CAAC,CAWtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACtC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,SAGxC,OAAO,CAAC,CAAC,KACb,QACD,OAAO,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,0BAA0B,CAAC,CAAC,CAClE,CAYF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C;IACD,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC;CAC5E,CAuDA;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,SAAS,MAAM,EAC9D,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,GAAE,iBAA4B,EAClC,EAAE,GAAE,iBAA4B,GAC/B,gBAAgB,CAAC,OAAO,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,sBAAsB,CAKpE"}
package/dist/testUtils.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import WebSocket from 'isomorphic-ws';
2
2
  import { WebSocketServer } from 'ws';
3
- import { WebSocketTransport } from './transport/impls/ws';
3
+ import { WebSocketClientTransport } from './transport/impls/ws/client';
4
4
  import { msg, reply, } from './transport';
5
5
  import { pushable } from 'it-pushable';
6
6
  import { Err, UNCAUGHT_ERROR, } from './router/result';
7
+ import { WebSocketServerTransport } from './transport/impls/ws/server';
7
8
  /**
8
9
  * Creates a WebSocket server instance using the provided HTTP server.
9
10
  * Only used as helper for testing.
@@ -48,23 +49,13 @@ export async function createLocalWebSocketClient(port) {
48
49
  * Creates a pair of WebSocket transports for testing purposes.
49
50
  * @param port - The port number to use for the client transport. This should be acquired after starting a server via {@link createWebSocketServer}.
50
51
  * @param wss - The WebSocketServer instance to use for the server transport.
51
- * @returns An array containing the client and server {@link WebSocketTransport} instances.
52
+ * @returns An array containing the client and server {@link WebSocketClientTransport} instances.
52
53
  */
53
54
  export function createWsTransports(port, wss, codec) {
54
55
  const options = codec ? { codec } : undefined;
55
56
  return [
56
- new WebSocketTransport(async () => {
57
- return createLocalWebSocketClient(port);
58
- }, 'client', options),
59
- new WebSocketTransport(async () => {
60
- return new Promise((resolve) => {
61
- wss.on('connection', async function onConnect(serverSock) {
62
- wss.removeListener('connection', onConnect);
63
- serverSock.binaryType = 'arraybuffer';
64
- resolve(serverSock);
65
- });
66
- });
67
- }, 'SERVER', options),
57
+ new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client', 'SERVER', options),
58
+ new WebSocketServerTransport(wss, 'SERVER', options),
68
59
  ];
69
60
  }
70
61
  /**
@@ -149,8 +140,8 @@ export function asClientStream(state, proc, extendedContext) {
149
140
  * @param streamId - The optional stream ID.
150
141
  * @returns The transport message.
151
142
  */
152
- export function payloadToTransportMessage(payload, streamId) {
153
- return msg('client', 'SERVER', 'service', 'procedure', streamId ?? 'stream', payload);
143
+ export function payloadToTransportMessage(payload, streamId, from = 'client', to = 'SERVER') {
144
+ return msg(from, to, 'service', 'procedure', streamId ?? 'stream', payload);
154
145
  }
155
146
  /**
156
147
  * Creates a dummy opaque transport message for testing purposes.
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ import { Codec } from '../../../codec';
3
+ import { TransportClientId } from '../../message';
4
+ import { Connection, Transport } from '../../transport';
5
+ export declare class StdioConnection extends Connection {
6
+ /**
7
+ * The writable stream to use as output.
8
+ */
9
+ output: NodeJS.WritableStream;
10
+ constructor(transport: Transport<StdioConnection>, connectedTo: TransportClientId, output: NodeJS.WritableStream);
11
+ send(payload: Uint8Array): boolean;
12
+ close(): Promise<void>;
13
+ }
14
+ interface Options {
15
+ codec: Codec;
16
+ }
17
+ /**
18
+ * A transport implementation that uses standard input and output streams.
19
+ * Can only be used 1:1, not N:1
20
+ * @extends Transport
21
+ */
22
+ export declare class StdioTransport extends Transport<StdioConnection> {
23
+ clientId: TransportClientId;
24
+ input: NodeJS.ReadableStream;
25
+ output: NodeJS.WritableStream;
26
+ /**
27
+ * Constructs a new StdioTransport instance.
28
+ * @param clientId - The ID of the client associated with this transport.
29
+ * @param input - The readable stream to use as input. Defaults to process.stdin.
30
+ * @param output - The writable stream to use as output. Defaults to process.stdout.
31
+ */
32
+ constructor(clientId: TransportClientId, input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream, providedOptions?: Partial<Options>);
33
+ setupConnectionStatusListeners(): void;
34
+ createNewConnection(to: TransportClientId): Promise<void>;
35
+ }
36
+ export {};
37
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../../transport/impls/stdio/stdio.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKxD,qBAAa,eAAgB,SAAQ,UAAU;IAC7C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;gBAG5B,SAAS,EAAE,SAAS,CAAC,eAAe,CAAC,EACrC,WAAW,EAAE,iBAAiB,EAC9B,MAAM,EAAE,MAAM,CAAC,cAAc;IAM/B,IAAI,CAAC,OAAO,EAAE,UAAU;IAOlB,KAAK;CAIZ;AAED,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAMD;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,SAAS,CAAC,eAAe,CAAC;IAC5D,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC,cAAc,CAAiB;IAC7C,MAAM,EAAE,MAAM,CAAC,cAAc,CAAkB;IAE/C;;;;;OAKG;gBAED,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,GAAE,MAAM,CAAC,cAA8B,EAC5C,MAAM,GAAE,MAAM,CAAC,cAA+B,EAC9C,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAUpC,8BAA8B,IAAI,IAAI;IAwBhC,mBAAmB,CAAC,EAAE,EAAE,iBAAiB;CAShD"}
@@ -0,0 +1,80 @@
1
+ import { NaiveJsonCodec } from '../../../codec/json';
2
+ import { log } from '../../../logging';
3
+ import { Connection, Transport } from '../../transport';
4
+ import readline from 'readline';
5
+ const newlineBuff = new TextEncoder().encode('\n');
6
+ export class StdioConnection extends Connection {
7
+ /**
8
+ * The writable stream to use as output.
9
+ */
10
+ output;
11
+ constructor(transport, connectedTo, output) {
12
+ super(transport, connectedTo);
13
+ this.output = output;
14
+ }
15
+ send(payload) {
16
+ const out = new Uint8Array(payload.length + newlineBuff.length);
17
+ out.set(payload, 0);
18
+ out.set(newlineBuff, payload.length);
19
+ return this.output.write(out);
20
+ }
21
+ async close() {
22
+ this.transport.onDisconnect(this);
23
+ this.output.end();
24
+ }
25
+ }
26
+ const defaultOptions = {
27
+ codec: NaiveJsonCodec,
28
+ };
29
+ /**
30
+ * A transport implementation that uses standard input and output streams.
31
+ * Can only be used 1:1, not N:1
32
+ * @extends Transport
33
+ */
34
+ export class StdioTransport extends Transport {
35
+ clientId;
36
+ input = process.stdin;
37
+ output = process.stdout;
38
+ /**
39
+ * Constructs a new StdioTransport instance.
40
+ * @param clientId - The ID of the client associated with this transport.
41
+ * @param input - The readable stream to use as input. Defaults to process.stdin.
42
+ * @param output - The writable stream to use as output. Defaults to process.stdout.
43
+ */
44
+ constructor(clientId, input = process.stdin, output = process.stdout, providedOptions) {
45
+ const options = { ...defaultOptions, ...providedOptions };
46
+ super(options.codec, clientId);
47
+ this.clientId = clientId;
48
+ this.input = input;
49
+ this.output = output;
50
+ this.setupConnectionStatusListeners();
51
+ }
52
+ setupConnectionStatusListeners() {
53
+ let conn = undefined;
54
+ const rl = readline.createInterface({
55
+ input: this.input,
56
+ });
57
+ const encoder = new TextEncoder();
58
+ rl.on('line', (msg) => {
59
+ const parsedMsg = this.parseMsg(encoder.encode(msg));
60
+ if (parsedMsg && !this.connections.has(parsedMsg.from)) {
61
+ conn = new StdioConnection(this, parsedMsg.from, this.output);
62
+ this.onConnect(conn);
63
+ }
64
+ this.handleMsg(parsedMsg);
65
+ });
66
+ rl.on('close', () => {
67
+ if (conn) {
68
+ this.onDisconnect(conn);
69
+ }
70
+ });
71
+ }
72
+ async createNewConnection(to) {
73
+ if (this.state === 'destroyed') {
74
+ throw new Error('cant reopen a destroyed connection');
75
+ }
76
+ log?.info(`${this.clientId} -- establishing a new stream to ${to}`);
77
+ const conn = new StdioConnection(this, to, this.output);
78
+ this.onConnect(conn);
79
+ }
80
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stdio.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.test.d.ts","sourceRoot":"","sources":["../../../../transport/impls/stdio/stdio.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import stream from 'node:stream';
3
+ import { StdioTransport } from './stdio';
4
+ import { waitForMessage } from '../..';
5
+ import { payloadToTransportMessage } from '../../../testUtils';
6
+ describe('sending and receiving across node streams works', () => {
7
+ test('basic send/receive', async () => {
8
+ const clientToServer = new stream.PassThrough();
9
+ const serverToClient = new stream.PassThrough();
10
+ const serverTransport = new StdioTransport('abc', clientToServer, serverToClient);
11
+ const clientTransport = new StdioTransport('def', serverToClient, clientToServer);
12
+ const msg = {
13
+ msg: 'cool',
14
+ test: 123,
15
+ };
16
+ const p = waitForMessage(serverTransport);
17
+ clientTransport.send(payloadToTransportMessage(msg, 'stream', clientTransport.clientId, serverTransport.clientId));
18
+ await expect(p).resolves.toStrictEqual(msg);
19
+ });
20
+ });
@@ -0,0 +1,45 @@
1
+ /// <reference types="ws" />
2
+ import WebSocket from 'isomorphic-ws';
3
+ import { Transport } from '../../transport';
4
+ import { TransportClientId } from '../../message';
5
+ import { type Codec } from '../../../codec';
6
+ import { WebSocketConnection } from './connection';
7
+ interface Options {
8
+ retryIntervalMs: number;
9
+ retryAttemptsMax: number;
10
+ codec: Codec;
11
+ }
12
+ type WebSocketResult = {
13
+ ws: WebSocket;
14
+ } | {
15
+ err: string;
16
+ };
17
+ /**
18
+ * A transport implementation that uses a WebSocket connection with automatic reconnection.
19
+ * @class
20
+ * @extends Transport
21
+ */
22
+ export declare class WebSocketClientTransport extends Transport<WebSocketConnection> {
23
+ /**
24
+ * A function that returns a Promise that resolves to a WebSocket instance.
25
+ */
26
+ wsGetter: (to: TransportClientId) => Promise<WebSocket>;
27
+ options: Options;
28
+ serverId: TransportClientId;
29
+ reconnectPromises: Map<TransportClientId, Promise<WebSocketResult>>;
30
+ /**
31
+ * Creates a new WebSocketTransport instance.
32
+ * @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
33
+ * @param clientId The ID of the client using the transport.
34
+ * @param providedOptions An optional object containing configuration options for the transport.
35
+ */
36
+ constructor(wsGetter: () => Promise<WebSocket>, clientId: TransportClientId, serverId: TransportClientId, providedOptions?: Partial<Options>);
37
+ setupConnectionStatusListeners(): void;
38
+ createNewConnection(to: string, attempt?: number): Promise<void>;
39
+ /**
40
+ * Begins a new attempt to establish a WebSocket connection.
41
+ */
42
+ open(): Promise<void>;
43
+ }
44
+ export {};
45
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/client.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,UAAU,OAAO;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;CACd;AAQD,KAAK,eAAe,GAAG;IAAE,EAAE,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,SAAS,CAAC,mBAAmB,CAAC;IAC1E;;OAEG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,iBAAiB,CAAC;IAE5B,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;IAEpE;;;;;OAKG;gBAED,QAAQ,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAClC,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAWpC,8BAA8B,IAAI,IAAI;IAIhC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,SAAI;IAmEjD;;OAEG;IACG,IAAI;CAGX"}
@@ -0,0 +1,102 @@
1
+ import { Transport } from '../../transport';
2
+ import { NaiveJsonCodec } from '../../../codec/json';
3
+ import { log } from '../../../logging';
4
+ import { WebSocketConnection } from './connection';
5
+ const defaultOptions = {
6
+ retryIntervalMs: 250,
7
+ retryAttemptsMax: 5,
8
+ codec: NaiveJsonCodec,
9
+ };
10
+ /**
11
+ * A transport implementation that uses a WebSocket connection with automatic reconnection.
12
+ * @class
13
+ * @extends Transport
14
+ */
15
+ export class WebSocketClientTransport extends Transport {
16
+ /**
17
+ * A function that returns a Promise that resolves to a WebSocket instance.
18
+ */
19
+ wsGetter;
20
+ options;
21
+ serverId;
22
+ reconnectPromises;
23
+ /**
24
+ * Creates a new WebSocketTransport instance.
25
+ * @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
26
+ * @param clientId The ID of the client using the transport.
27
+ * @param providedOptions An optional object containing configuration options for the transport.
28
+ */
29
+ constructor(wsGetter, clientId, serverId, providedOptions) {
30
+ const options = { ...defaultOptions, ...providedOptions };
31
+ super(options.codec, clientId);
32
+ this.wsGetter = wsGetter;
33
+ this.serverId = serverId;
34
+ this.options = options;
35
+ this.reconnectPromises = new Map();
36
+ this.setupConnectionStatusListeners();
37
+ }
38
+ setupConnectionStatusListeners() {
39
+ this.createNewConnection(this.serverId);
40
+ }
41
+ async createNewConnection(to, attempt = 0) {
42
+ if (this.state === 'destroyed') {
43
+ throw new Error('cant reopen a destroyed connection');
44
+ }
45
+ let reconnectPromise = this.reconnectPromises.get(to);
46
+ if (!reconnectPromise) {
47
+ reconnectPromise = new Promise(async (resolve) => {
48
+ log?.info(`${this.clientId} -- establishing a new websocket to ${to}`);
49
+ const ws = await this.wsGetter(to);
50
+ if (ws.readyState === ws.OPEN) {
51
+ return resolve({ ws });
52
+ }
53
+ if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
54
+ return resolve({ err: 'ws is closing or closed' });
55
+ }
56
+ ws.addEventListener('open', function onOpen() {
57
+ ws.removeEventListener('open', onOpen);
58
+ resolve({ ws });
59
+ });
60
+ ws.addEventListener('close', function onClose(evt) {
61
+ ws.removeEventListener('close', onClose);
62
+ resolve({ err: evt.reason });
63
+ });
64
+ });
65
+ this.reconnectPromises.set(to, reconnectPromise);
66
+ }
67
+ const res = await reconnectPromise;
68
+ if ('ws' in res && res.ws.readyState === res.ws.OPEN) {
69
+ if (res.ws === this.connections.get(to)?.ws) {
70
+ // this is our current connection
71
+ // we reach this state when createNewConnection is called multiple times
72
+ // concurrently
73
+ return;
74
+ }
75
+ log?.info(`${this.clientId} -- websocket ok`);
76
+ const conn = new WebSocketConnection(this, to, res.ws);
77
+ this.onConnect(conn);
78
+ res.ws.onclose = () => {
79
+ this.reconnectPromises.delete(to);
80
+ this.onDisconnect(conn);
81
+ };
82
+ this.state = 'open';
83
+ return;
84
+ }
85
+ // otherwise try and reconnect again
86
+ log?.warn(`${this.clientId} -- websocket failed, trying again in ${this.options.retryIntervalMs}ms`);
87
+ this.reconnectPromises.delete(to);
88
+ if (attempt >= this.options.retryAttemptsMax) {
89
+ return;
90
+ }
91
+ else {
92
+ // linear backoff
93
+ setTimeout(() => this.createNewConnection(to, attempt + 1), this.options.retryIntervalMs * attempt);
94
+ }
95
+ }
96
+ /**
97
+ * Begins a new attempt to establish a WebSocket connection.
98
+ */
99
+ async open() {
100
+ return this.createNewConnection(this.serverId);
101
+ }
102
+ }