@replit/river 0.7.2 → 0.8.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.
Files changed (56) hide show
  1. package/README.md +2 -0
  2. package/dist/__tests__/bandwidth.bench.js +6 -6
  3. package/dist/__tests__/e2e.test.js +112 -23
  4. package/dist/__tests__/fixtures/cleanup.d.ts +12 -0
  5. package/dist/__tests__/fixtures/cleanup.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/cleanup.js +39 -0
  7. package/dist/__tests__/fixtures/observable.d.ts +26 -0
  8. package/dist/__tests__/fixtures/observable.d.ts.map +1 -0
  9. package/dist/__tests__/fixtures/observable.js +38 -0
  10. package/dist/__tests__/fixtures/observable.test.d.ts +2 -0
  11. package/dist/__tests__/fixtures/observable.test.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/observable.test.js +39 -0
  13. package/dist/__tests__/{fixtures.d.ts → fixtures/services.d.ts} +61 -18
  14. package/dist/__tests__/fixtures/services.d.ts.map +1 -0
  15. package/dist/__tests__/{fixtures.js → fixtures/services.js} +35 -3
  16. package/dist/__tests__/handler.test.js +24 -7
  17. package/dist/__tests__/invariants.test.d.ts +2 -0
  18. package/dist/__tests__/invariants.test.d.ts.map +1 -0
  19. package/dist/__tests__/invariants.test.js +136 -0
  20. package/dist/__tests__/serialize.test.js +2 -1
  21. package/dist/router/builder.d.ts +10 -4
  22. package/dist/router/builder.d.ts.map +1 -1
  23. package/dist/router/client.d.ts +14 -5
  24. package/dist/router/client.d.ts.map +1 -1
  25. package/dist/router/client.js +42 -11
  26. package/dist/router/server.d.ts +14 -0
  27. package/dist/router/server.d.ts.map +1 -1
  28. package/dist/router/server.js +85 -48
  29. package/dist/transport/impls/stdio/stdio.d.ts +0 -4
  30. package/dist/transport/impls/stdio/stdio.d.ts.map +1 -1
  31. package/dist/transport/impls/stdio/stdio.js +0 -5
  32. package/dist/transport/impls/stdio/stdio.test.js +6 -1
  33. package/dist/transport/impls/ws/client.d.ts.map +1 -1
  34. package/dist/transport/impls/ws/client.js +2 -2
  35. package/dist/transport/impls/ws/connection.d.ts.map +1 -1
  36. package/dist/transport/impls/ws/connection.js +2 -1
  37. package/dist/transport/impls/ws/server.d.ts +0 -2
  38. package/dist/transport/impls/ws/server.d.ts.map +1 -1
  39. package/dist/transport/impls/ws/server.js +4 -9
  40. package/dist/transport/impls/ws/ws.test.js +31 -11
  41. package/dist/transport/index.d.ts +3 -3
  42. package/dist/transport/index.d.ts.map +1 -1
  43. package/dist/transport/index.js +1 -1
  44. package/dist/transport/message.d.ts +10 -0
  45. package/dist/transport/message.d.ts.map +1 -1
  46. package/dist/transport/message.js +15 -0
  47. package/dist/transport/transport.d.ts +37 -11
  48. package/dist/transport/transport.d.ts.map +1 -1
  49. package/dist/transport/transport.js +37 -13
  50. package/dist/{testUtils.d.ts → util/testHelpers.d.ts} +27 -8
  51. package/dist/util/testHelpers.d.ts.map +1 -0
  52. package/dist/{testUtils.js → util/testHelpers.js} +51 -5
  53. package/package.json +3 -3
  54. package/dist/__tests__/fixtures.d.ts.map +0 -1
  55. package/dist/testUtils.d.ts.map +0 -1
  56. /package/dist/__tests__/{largePayload.json → fixtures/largePayload.json} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { pushable } from 'it-pushable';
2
- import { ControlMessagePayloadSchema, isStreamClose, isStreamOpen, reply, } from '../transport/message';
2
+ import { ControlMessagePayloadSchema, isStreamClose, isStreamOpen, reply, closeStream, } 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';
@@ -15,6 +15,16 @@ export async function createServer(transport, services, extendedContext) {
15
15
  const contextMap = new Map();
16
16
  // map of streamId to ProcStream
17
17
  const streamMap = new Map();
18
+ async function cleanupStream(id) {
19
+ const stream = streamMap.get(id);
20
+ if (stream) {
21
+ stream.incoming.end();
22
+ await stream.promises.inputHandler;
23
+ stream.outgoing.end();
24
+ await stream.promises.outputHandler;
25
+ streamMap.delete(id);
26
+ }
27
+ }
18
28
  function getContext(service) {
19
29
  const context = contextMap.get(service);
20
30
  if (!context) {
@@ -28,92 +38,119 @@ export async function createServer(transport, services, extendedContext) {
28
38
  for (const service of Object.values(services)) {
29
39
  contextMap.set(service, { ...extendedContext, state: service.state });
30
40
  }
31
- const handler = async (msg) => {
32
- if (msg.to !== transport.clientId) {
41
+ const handler = async (message) => {
42
+ if (message.to !== transport.clientId) {
33
43
  log?.info(`${transport.clientId} -- got msg with destination that isn't the server, ignoring`);
34
44
  return;
35
45
  }
36
- if (!(msg.serviceName in services)) {
37
- log?.warn(`${transport.clientId} -- couldn't find service ${msg.serviceName}`);
46
+ if (!(message.serviceName in services)) {
47
+ log?.warn(`${transport.clientId} -- couldn't find service ${message.serviceName}`);
38
48
  return;
39
49
  }
40
- const service = services[msg.serviceName];
50
+ const service = services[message.serviceName];
41
51
  const serviceContext = getContext(service);
42
- if (!(msg.procedureName in service.procedures)) {
43
- log?.warn(`${transport.clientId} -- couldn't find a matching procedure for ${msg.serviceName}.${msg.procedureName}`);
52
+ if (!(message.procedureName in service.procedures)) {
53
+ log?.warn(`${transport.clientId} -- couldn't find a matching procedure for ${message.serviceName}.${message.procedureName}`);
44
54
  return;
45
55
  }
46
- const procedure = service.procedures[msg.procedureName];
47
- if (isStreamOpen(msg.controlFlags)) {
56
+ const procedure = service.procedures[message.procedureName];
57
+ const streamIdx = `${message.serviceName}.${message.procedureName}:${message.streamId}`;
58
+ if (isStreamOpen(message.controlFlags) && !streamMap.has(streamIdx)) {
48
59
  const incoming = pushable({ objectMode: true });
49
60
  const outgoing = pushable({ objectMode: true });
50
- const openPromises = [
51
- // sending outgoing messages back to client
52
- (async () => {
53
- for await (const response of outgoing) {
54
- transport.send(response);
55
- }
56
- })(),
57
- ];
61
+ const outputHandler =
62
+ // sending outgoing messages back to client
63
+ (async () => {
64
+ for await (const response of outgoing) {
65
+ transport.send(response);
66
+ }
67
+ // we ended, send a close bit back to the client
68
+ // only subscriptions and streams have streams the
69
+ // handler can close
70
+ if (procedure.type === 'subscription' ||
71
+ procedure.type === 'stream') {
72
+ transport.send(closeStream(transport.clientId, message.from, message.serviceName, message.procedureName, message.streamId));
73
+ }
74
+ })();
58
75
  function errorHandler(err) {
59
76
  const errorMsg = err instanceof Error ? err.message : `[coerced to error] ${err}`;
60
- log?.error(`${transport.clientId} -- procedure ${msg.serviceName}.${msg.procedureName}:${msg.streamId} threw an error: ${errorMsg}`);
61
- outgoing.push(reply(msg, Err({
77
+ log?.error(`${transport.clientId} -- procedure ${message.serviceName}.${message.procedureName}:${message.streamId} threw an error: ${errorMsg}`);
78
+ outgoing.push(reply(message, Err({
62
79
  code: UNCAUGHT_ERROR,
63
80
  message: errorMsg,
64
81
  })));
65
82
  }
66
83
  // pump incoming message stream -> handler -> outgoing message stream
84
+ let inputHandler;
67
85
  if (procedure.type === 'stream') {
68
- openPromises.push(procedure
86
+ inputHandler = procedure
69
87
  .handler(serviceContext, incoming, outgoing)
70
- .catch(errorHandler));
88
+ .catch(errorHandler);
71
89
  }
72
90
  else if (procedure.type === 'rpc') {
73
- openPromises.push((async () => {
74
- for await (const inputMessage of incoming) {
75
- try {
76
- const outputMessage = await procedure.handler(serviceContext, inputMessage);
77
- outgoing.push(outputMessage);
78
- }
79
- catch (err) {
80
- errorHandler(err);
81
- }
91
+ inputHandler = (async () => {
92
+ const inputMessage = await incoming.next();
93
+ if (inputMessage.done) {
94
+ return;
95
+ }
96
+ try {
97
+ const outputMessage = await procedure.handler(serviceContext, inputMessage.value);
98
+ outgoing.push(outputMessage);
99
+ }
100
+ catch (err) {
101
+ errorHandler(err);
82
102
  }
83
- })());
103
+ })();
104
+ }
105
+ else if (procedure.type === 'subscription') {
106
+ inputHandler = (async () => {
107
+ const inputMessage = await incoming.next();
108
+ if (inputMessage.done) {
109
+ return;
110
+ }
111
+ try {
112
+ await procedure.handler(serviceContext, inputMessage.value, outgoing);
113
+ }
114
+ catch (err) {
115
+ errorHandler(err);
116
+ }
117
+ })();
118
+ }
119
+ else {
120
+ // procedure is inferred to be never here as this is not a valid procedure type
121
+ // we cast just to log
122
+ log?.warn(`${transport.clientId} -- got request for invalid procedure type ${procedure.type} at ${message.serviceName}.${message.procedureName}`);
123
+ return;
84
124
  }
85
- streamMap.set(`${msg.serviceName}.${msg.procedureName}:${msg.streamId}`, {
125
+ streamMap.set(streamIdx, {
86
126
  incoming,
87
127
  outgoing,
88
- openPromises,
128
+ promises: { inputHandler, outputHandler },
89
129
  });
90
130
  }
91
- const procStream = streamMap.get(`${msg.serviceName}.${msg.procedureName}:${msg.streamId}`);
131
+ const procStream = streamMap.get(streamIdx);
92
132
  if (!procStream) {
93
- log?.warn(`${transport.clientId} -- couldn't find a matching procedure stream for ${msg.serviceName}.${msg.procedureName}:${msg.streamId}`);
133
+ log?.warn(`${transport.clientId} -- couldn't find a matching procedure stream for ${message.serviceName}.${message.procedureName}:${message.streamId}`);
94
134
  return;
95
135
  }
96
- if (Value.Check(procedure.input, msg.payload)) {
97
- procStream.incoming.push(msg);
136
+ if (Value.Check(procedure.input, message.payload)) {
137
+ procStream.incoming.push(message);
98
138
  }
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)}`);
139
+ else if (!Value.Check(ControlMessagePayloadSchema, message.payload)) {
140
+ log?.error(`${transport.clientId} -- procedure ${message.serviceName}.${message.procedureName} received invalid payload: ${JSON.stringify(message.payload)}`);
101
141
  }
102
- if (isStreamClose(msg.controlFlags)) {
103
- procStream.incoming.end();
104
- await Promise.all(procStream.openPromises);
105
- procStream.outgoing.end();
142
+ if (isStreamClose(message.controlFlags)) {
143
+ await cleanupStream(streamIdx);
106
144
  }
107
145
  };
108
146
  transport.addMessageListener(handler);
109
147
  return {
110
148
  services,
149
+ streams: streamMap,
111
150
  async close() {
112
151
  transport.removeMessageListener(handler);
113
- for (const [_, stream] of streamMap) {
114
- stream.incoming.end();
115
- await Promise.all(stream.openPromises);
116
- stream.outgoing.end();
152
+ for (const streamIdx of streamMap.keys()) {
153
+ await cleanupStream(streamIdx);
117
154
  }
118
155
  },
119
156
  };
@@ -3,9 +3,6 @@ import { Codec } from '../../../codec';
3
3
  import { TransportClientId } from '../../message';
4
4
  import { Connection, Transport } from '../../transport';
5
5
  export declare class StdioConnection extends Connection {
6
- /**
7
- * The writable stream to use as output.
8
- */
9
6
  output: NodeJS.WritableStream;
10
7
  constructor(transport: Transport<StdioConnection>, connectedTo: TransportClientId, output: NodeJS.WritableStream);
11
8
  send(payload: Uint8Array): boolean;
@@ -16,7 +13,6 @@ interface Options {
16
13
  }
17
14
  /**
18
15
  * A transport implementation that uses standard input and output streams.
19
- * Can only be used 1:1, not N:1
20
16
  * @extends Transport
21
17
  */
22
18
  export declare class StdioTransport extends Transport<StdioConnection> {
@@ -1 +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"}
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,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;CAGZ;AAED,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAMD;;;GAGG;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"}
@@ -4,9 +4,6 @@ import { Connection, Transport } from '../../transport';
4
4
  import readline from 'readline';
5
5
  const newlineBuff = new TextEncoder().encode('\n');
6
6
  export class StdioConnection extends Connection {
7
- /**
8
- * The writable stream to use as output.
9
- */
10
7
  output;
11
8
  constructor(transport, connectedTo, output) {
12
9
  super(transport, connectedTo);
@@ -19,7 +16,6 @@ export class StdioConnection extends Connection {
19
16
  return this.output.write(out);
20
17
  }
21
18
  async close() {
22
- this.transport.onDisconnect(this);
23
19
  this.output.end();
24
20
  }
25
21
  }
@@ -28,7 +24,6 @@ const defaultOptions = {
28
24
  };
29
25
  /**
30
26
  * A transport implementation that uses standard input and output streams.
31
- * Can only be used 1:1, not N:1
32
27
  * @extends Transport
33
28
  */
34
29
  export class StdioTransport extends Transport {
@@ -2,7 +2,8 @@ import { describe, test, expect } from 'vitest';
2
2
  import stream from 'node:stream';
3
3
  import { StdioTransport } from './stdio';
4
4
  import { waitForMessage } from '../..';
5
- import { payloadToTransportMessage } from '../../../testUtils';
5
+ import { payloadToTransportMessage } from '../../../util/testHelpers';
6
+ import { ensureTransportIsClean } from '../../../__tests__/fixtures/cleanup';
6
7
  describe('sending and receiving across node streams works', () => {
7
8
  test('basic send/receive', async () => {
8
9
  const clientToServer = new stream.PassThrough();
@@ -16,5 +17,9 @@ describe('sending and receiving across node streams works', () => {
16
17
  const p = waitForMessage(serverTransport);
17
18
  clientTransport.send(payloadToTransportMessage(msg, 'stream', clientTransport.clientId, serverTransport.clientId));
18
19
  await expect(p).resolves.toStrictEqual(msg);
20
+ await clientTransport.close();
21
+ await serverTransport.close();
22
+ await ensureTransportIsClean(clientTransport);
23
+ await ensureTransportIsClean(serverTransport);
19
24
  });
20
25
  });
@@ -1 +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"}
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;IAuEjD;;OAEG;IACG,IAAI;CAGX"}
@@ -83,13 +83,13 @@ export class WebSocketClientTransport extends Transport {
83
83
  return;
84
84
  }
85
85
  // otherwise try and reconnect again
86
- log?.warn(`${this.clientId} -- websocket failed, trying again in ${this.options.retryIntervalMs}ms`);
87
86
  this.reconnectPromises.delete(to);
88
87
  if (attempt >= this.options.retryAttemptsMax) {
89
- return;
88
+ throw new Error(`${this.clientId} -- websocket to ${to} failed after ${attempt} attempts, giving up`);
90
89
  }
91
90
  else {
92
91
  // linear backoff
92
+ log?.warn(`${this.clientId} -- websocket to ${to} failed, trying again in ${this.options.retryIntervalMs * attempt}ms`);
93
93
  setTimeout(() => this.createNewConnection(to, attempt + 1), this.options.retryIntervalMs * attempt);
94
94
  }
95
95
  }
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/connection.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,SAAS,MAAM,eAAe,CAAC;AAEtC,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,EAAE,EAAE,SAAS,CAAC;gBAGZ,SAAS,EAAE,SAAS,CAAC,mBAAmB,CAAC,EACzC,WAAW,EAAE,iBAAiB,EAC9B,EAAE,EAAE,SAAS;IAQf,IAAI,CAAC,OAAO,EAAE,UAAU;IASlB,KAAK;CAGZ"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/connection.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,SAAS,MAAM,eAAe,CAAC;AAEtC,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,EAAE,EAAE,SAAS,CAAC;gBAGZ,SAAS,EAAE,SAAS,CAAC,mBAAmB,CAAC,EACzC,WAAW,EAAE,iBAAiB,EAC9B,EAAE,EAAE,SAAS;IAUf,IAAI,CAAC,OAAO,EAAE,UAAU;IASlB,KAAK;CAGZ"}
@@ -5,7 +5,8 @@ export class WebSocketConnection extends Connection {
5
5
  super(transport, connectedTo);
6
6
  this.ws = ws;
7
7
  ws.binaryType = 'arraybuffer';
8
- this.ws.onmessage = (msg) => this.onMessage(msg.data);
8
+ // take over the onmessage for this websocket
9
+ this.ws.onmessage = (msg) => transport.onMessage(msg.data);
9
10
  }
10
11
  send(payload) {
11
12
  if (this.ws.readyState === this.ws.OPEN) {
@@ -12,8 +12,6 @@ export declare class WebSocketServerTransport extends Transport<WebSocketConnect
12
12
  constructor(wss: Server, clientId: TransportClientId, providedOptions?: Partial<Options>);
13
13
  setupConnectionStatusListeners(): void;
14
14
  createNewConnection(to: string): Promise<void>;
15
- destroy(): Promise<void>;
16
- close(): Promise<void>;
17
15
  }
18
16
  export {};
19
17
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAMD,qBAAa,wBAAyB,SAAQ,SAAS,CAAC,mBAAmB,CAAC;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,iBAAiB,CAAC;gBAG1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IASpC,8BAA8B,IAAI,IAAI;IA8BhC,mBAAmB,CAAC,EAAE,EAAE,MAAM;IAM9B,OAAO;IAKP,KAAK;CAIZ"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAMD,qBAAa,wBAAyB,SAAQ,SAAS,CAAC,mBAAmB,CAAC;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,iBAAiB,CAAC;gBAG1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IASpC,8BAA8B,IAAI,IAAI;IAiChC,mBAAmB,CAAC,EAAE,EAAE,MAAM;CAKrC"}
@@ -19,8 +19,11 @@ export class WebSocketServerTransport extends Transport {
19
19
  this.wss.on('connection', (ws) => {
20
20
  let conn = undefined;
21
21
  ws.onmessage = (msg) => {
22
+ // when we establish WebSocketConnection, ws.onmessage
23
+ // gets overriden so this only runs on the first valid message
24
+ // the websocket receives
22
25
  const parsedMsg = this.parseMsg(msg.data);
23
- if (parsedMsg) {
26
+ if (parsedMsg && !conn) {
24
27
  conn = new WebSocketConnection(this, parsedMsg.from, ws);
25
28
  this.onConnect(conn);
26
29
  this.handleMsg(parsedMsg);
@@ -42,12 +45,4 @@ export class WebSocketServerTransport extends Transport {
42
45
  log?.warn(err);
43
46
  return;
44
47
  }
45
- async destroy() {
46
- super.destroy();
47
- this.wss.close();
48
- }
49
- async close() {
50
- super.close();
51
- this.wss.close();
52
- }
53
48
  }
@@ -1,24 +1,27 @@
1
1
  import http from 'http';
2
2
  import { describe, test, expect, afterAll } from 'vitest';
3
- import { createWebSocketServer, createWsTransports, createDummyTransportMessage, onServerReady, createLocalWebSocketClient, } from '../../../testUtils';
3
+ import { createWebSocketServer, createWsTransports, createDummyTransportMessage, onServerReady, createLocalWebSocketClient, } from '../../../util/testHelpers';
4
4
  import { msg, waitForMessage } from '../..';
5
5
  import { WebSocketServerTransport } from './server';
6
6
  import { WebSocketClientTransport } from './client';
7
+ import { testFinishesCleanly } from '../../../__tests__/fixtures/cleanup';
7
8
  describe('sending and receiving across websockets works', async () => {
8
9
  const server = http.createServer();
9
10
  const port = await onServerReady(server);
10
11
  const wss = await createWebSocketServer(server);
11
12
  afterAll(() => {
12
- wss.clients.forEach((socket) => {
13
- socket.close();
14
- });
13
+ wss.close();
15
14
  server.close();
16
15
  });
17
16
  test('basic send/receive', async () => {
18
17
  const [clientTransport, serverTransport] = createWsTransports(port, wss);
19
18
  const msg = createDummyTransportMessage();
20
19
  clientTransport.send(msg);
21
- return expect(waitForMessage(serverTransport, (recv) => recv.id === msg.id)).resolves.toStrictEqual(msg.payload);
20
+ await expect(waitForMessage(serverTransport, (recv) => recv.id === msg.id)).resolves.toStrictEqual(msg.payload);
21
+ await testFinishesCleanly({
22
+ clientTransports: [clientTransport],
23
+ serverTransport,
24
+ });
22
25
  });
23
26
  test('sending respects to/from fields', async () => {
24
27
  const makeDummyMessage = (from, to, message) => {
@@ -50,6 +53,10 @@ describe('sending and receiving across websockets works', async () => {
50
53
  serverTransport.send(msg1);
51
54
  serverTransport.send(msg2);
52
55
  await expect(promises).resolves.toStrictEqual([msg1.payload, msg2.payload]);
56
+ await testFinishesCleanly({
57
+ clientTransports: [client1, client2],
58
+ serverTransport,
59
+ });
53
60
  });
54
61
  });
55
62
  describe('retry logic', async () => {
@@ -57,9 +64,7 @@ describe('retry logic', async () => {
57
64
  const port = await onServerReady(server);
58
65
  const wss = await createWebSocketServer(server);
59
66
  afterAll(() => {
60
- wss.clients.forEach((socket) => {
61
- socket.close();
62
- });
67
+ wss.close();
63
68
  server.close();
64
69
  });
65
70
  // TODO: right now, we only test client-side disconnects, we probably
@@ -73,7 +78,12 @@ describe('retry logic', async () => {
73
78
  await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
74
79
  clientTransport.connections.forEach((conn) => conn.ws.close());
75
80
  clientTransport.send(msg2);
76
- return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
81
+ // by this point the client should have reconnected
82
+ await expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
83
+ await testFinishesCleanly({
84
+ clientTransports: [clientTransport],
85
+ serverTransport,
86
+ });
77
87
  });
78
88
  test('ws transport is recreated after unclean disconnect', async () => {
79
89
  const [clientTransport, serverTransport] = createWsTransports(port, wss);
@@ -83,7 +93,13 @@ describe('retry logic', async () => {
83
93
  await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
84
94
  clientTransport.connections.forEach((conn) => conn.ws.terminate());
85
95
  clientTransport.send(msg2);
86
- return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
96
+ // by this point the client should have reconnected
97
+ await expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
98
+ // this is not expected to be clean because we destroyed the transport
99
+ await testFinishesCleanly({
100
+ clientTransports: [clientTransport],
101
+ serverTransport,
102
+ });
87
103
  });
88
104
  test('ws transport is not recreated after destroy', async () => {
89
105
  const [clientTransport, serverTransport] = createWsTransports(port, wss);
@@ -92,6 +108,10 @@ describe('retry logic', async () => {
92
108
  clientTransport.send(msg1);
93
109
  await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
94
110
  clientTransport.destroy();
95
- return expect(() => clientTransport.send(msg2)).toThrow(new Error('transport is destroyed, cant send'));
111
+ expect(() => clientTransport.send(msg2)).toThrow(new Error('transport is destroyed, cant send'));
112
+ // this is not expected to be clean because we destroyed the transport
113
+ expect(clientTransport.state).toEqual('destroyed');
114
+ await clientTransport.close();
115
+ await serverTransport.close();
96
116
  });
97
117
  });
@@ -1,8 +1,8 @@
1
1
  import { OpaqueTransportMessage } from './message';
2
- import { Connection, Transport } from './transport';
3
- export { Transport } from './transport';
2
+ import { Transport, Connection } from './transport';
3
+ export { Transport, Connection } from './transport';
4
4
  export { TransportMessageSchema, OpaqueTransportMessageSchema, msg, reply, } from './message';
5
- export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, } from './message';
5
+ export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, isStreamOpen, isStreamClose, } from './message';
6
6
  /**
7
7
  * Waits for a message from the transport.
8
8
  * @param {Transport} t - The transport to listen to.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,GAAG,EACH,KAAK,GACN,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,EACxB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,EACjD,cAAc,CAAC,EAAE,OAAO,oBAczB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,GAAG,EACH,KAAK,GACN,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,sBAAsB,EACtB,iBAAiB,EACjB,YAAY,EACZ,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,EACxB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,EACjD,cAAc,CAAC,EAAE,OAAO,oBAczB"}
@@ -1,5 +1,5 @@
1
1
  // re-export
2
- export { Transport } from './transport';
2
+ export { Transport, Connection } from './transport';
3
3
  export { TransportMessageSchema, OpaqueTransportMessageSchema, msg, reply, } from './message';
4
4
  /**
5
5
  * Waits for a message from the transport.
@@ -103,6 +103,16 @@ export declare function msg<Payload extends object>(from: string, to: string, se
103
103
  * @returns A new transport message with appropriate to, from, and payload fields
104
104
  */
105
105
  export declare function reply<Payload extends object>(msg: OpaqueTransportMessage, response: Payload): TransportMessage<Payload>;
106
+ /**
107
+ * Create a request to close a stream
108
+ * @param from The ID of the client initiating the close.
109
+ * @param to The ID of the client being closed.
110
+ * @param respondTo The transport message to respond to.
111
+ * @returns The close message
112
+ */
113
+ export declare function closeStream(from: TransportClientId, to: TransportClientId, service: string, proc: string, stream: string): TransportMessage<{
114
+ type: "CLOSE";
115
+ }>;
106
116
  /**
107
117
  * Checks if the given control flag (usually found in msg.controlFlag) is an ack message.
108
118
  * @param controlFlag - The control flag to check.
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../transport/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGlD;;;;;;GAMG;AACH,0BAAkB,YAAY;IAC5B,MAAM,IAAS;IACf,aAAa,IAAS;IACtB,eAAe,IAAS;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;EAU/B,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;EAI9B,CAAC;AAEF,eAAO,MAAM,2BAA2B;;EAEtC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;EAExC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAC1B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzE;IACF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,OAAO,SAAS,MAAM,EACxC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,gBAAgB,CAAC,OAAO,CAAC,CAW3B;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,OAAO,SAAS,MAAM,EAC1C,GAAG,EAAE,sBAAsB,EAC3B,QAAQ,EAAE,OAAO,GAChB,gBAAgB,CAAC,OAAO,CAAC,CAQ3B;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAK1D"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../transport/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAU,MAAM,mBAAmB,CAAC;AAG1D;;;;;;GAMG;AACH,0BAAkB,YAAY;IAC5B,MAAM,IAAS;IACf,aAAa,IAAS;IACtB,eAAe,IAAS;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;EAU/B,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;EAI9B,CAAC;AAEF,eAAO,MAAM,2BAA2B;;EAEtC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;EAExC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAC1B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzE;IACF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,OAAO,SAAS,MAAM,EACxC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,gBAAgB,CAAC,OAAO,CAAC,CAW3B;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,OAAO,SAAS,MAAM,EAC1C,GAAG,EAAE,sBAAsB,EAC3B,QAAQ,EAAE,OAAO,GAChB,gBAAgB,CAAC,OAAO,CAAC,CAS3B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,iBAAiB,EACvB,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;;GAOf;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAK1D"}
@@ -65,12 +65,27 @@ export function msg(from, to, service, proc, stream, payload) {
65
65
  export function reply(msg, response) {
66
66
  return {
67
67
  ...msg,
68
+ controlFlags: 0,
68
69
  id: nanoid(),
69
70
  to: msg.from,
70
71
  from: msg.to,
71
72
  payload: response,
72
73
  };
73
74
  }
75
+ /**
76
+ * Create a request to close a stream
77
+ * @param from The ID of the client initiating the close.
78
+ * @param to The ID of the client being closed.
79
+ * @param respondTo The transport message to respond to.
80
+ * @returns The close message
81
+ */
82
+ export function closeStream(from, to, service, proc, stream) {
83
+ const closeMessage = msg(from, to, service, proc, stream, {
84
+ type: 'CLOSE',
85
+ });
86
+ closeMessage.controlFlags |= 4 /* ControlFlags.StreamClosedBit */;
87
+ return closeMessage;
88
+ }
74
89
  /**
75
90
  * Checks if the given control flag (usually found in msg.controlFlag) is an ack message.
76
91
  * @param controlFlag - The control flag to check.
@@ -1,26 +1,52 @@
1
1
  import { Codec } from '../codec/types';
2
2
  import { MessageId, OpaqueTransportMessage, TransportClientId } from './message';
3
3
  /**
4
- * Abstract base for a connection between two nodes in a River network.
5
- * A connection is responsible for sending and receiving messages on a 1:1
6
- * basis between nodes.
7
- * Connections can be reused across different transports.
8
- * @abstract
4
+ * A 1:1 connection between two transports. Once this is created,
5
+ * the {@link Connection} is expected to take over responsibility for
6
+ * reading and writing messages from the underlying connection.
7
+ *
8
+ * 1) Messages received on the {@link Connection} are dispatched back to the {@link Transport}
9
+ * via {@link Transport.onMessage}. The {@link Transport} then notifies any registered message listeners.
10
+ * 2) When {@link Transport.send}(msg) is called, the transport looks up the appropriate
11
+ * connection in the {@link connections} map via `msg.to` and calls {@link send}(bytes)
12
+ * so the connection can send it.
9
13
  */
10
14
  export declare abstract class Connection {
11
15
  connectedTo: TransportClientId;
12
16
  transport: Transport<Connection>;
13
17
  constructor(transport: Transport<Connection>, connectedTo: TransportClientId);
14
- onMessage(msg: Uint8Array): void;
15
18
  abstract send(msg: Uint8Array): boolean;
16
- abstract close(): Promise<void>;
19
+ abstract close(): void;
17
20
  }
18
21
  export type TransportStatus = 'open' | 'closed' | 'destroyed';
19
22
  /**
20
- * Abstract base for a transport layer for communication between nodes in a River network.
21
- * A transport is responsible for handling the 1:n connection logic between nodes and
22
- * delegating sending/receiving to connections.
23
- * Any River transport methods need to implement this interface.
23
+ * Transports manage the lifecycle (creation/deletion) of connections. Its responsibilities include:
24
+ *
25
+ * 1) Constructing a new {@link Connection} on {@link TransportMessage}s from new clients.
26
+ * After constructing the {@link Connection}, {@link onConnect} is called which adds it to the connection map.
27
+ * 2) Delegating message listening of the connection to the newly created {@link Connection}.
28
+ * From this point on, the {@link Connection} is responsible for *reading* and *writing*
29
+ * messages from the connection.
30
+ * 3) When a connection is closed, the {@link Transport} calls {@link onDisconnect} which closes the
31
+ * connection via {@link Connection.close} and removes it from the {@link connections} map.
32
+
33
+ *
34
+ * ```plaintext
35
+ * ▲
36
+ * incoming │
37
+ * messages │
38
+ * ▼
39
+ * ┌─────────────┐ 1:N ┌────────────┐
40
+ * │ Transport │ ◄─────► │ Connection │
41
+ * └─────────────┘ └────────────┘
42
+ * ▲
43
+ * │
44
+ * ▼
45
+ * ┌───────────┐
46
+ * │ Message │
47
+ * │ Listeners │
48
+ * └───────────┘
49
+ * ```
24
50
  * @abstract
25
51
  */
26
52
  export declare abstract class Transport<ConnType extends Connection> {
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,OAAO,EAEL,SAAS,EACT,sBAAsB,EAGtB,iBAAiB,EAGlB,MAAM,WAAW,CAAC;AAGnB;;;;;;GAMG;AACH,8BAAsB,UAAU;IAC9B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;gBAG/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,WAAW,EAAE,iBAAiB;IAMhC,SAAS,CAAC,GAAG,EAAE,UAAU;IAIzB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IACvC,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D;;;;;;GAMG;AACH,8BAAsB,SAAS,CAAC,QAAQ,SAAS,UAAU;IACzD;;;OAGG;IACH,KAAK,EAAE,eAAe,CAAC;IAEvB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,iBAAiB,CAAC;IAE5B;;OAEG;IACH,eAAe,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,CAAC,CAAC;IAE5D;;;OAGG;IACH,SAAS,EAAE,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAEpD;;OAEG;IACH,UAAU,EAAE,GAAG,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAEnD;;OAEG;IACH,WAAW,EAAE,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAE9C;;;;OAIG;gBACS,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB;IAUrD;;;OAGG;IACH,QAAQ,CAAC,8BAA8B,IAAI,IAAI;IAE/C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAElE;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,QAAQ;IAyBxB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,QAAQ;IAM3B;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,UAAU;IAIzB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,sBAAsB,GAAG,IAAI;IAmBlE;;;;OAIG;IACH,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI;IAgCtD;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAIxE;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAI3E;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IA4C5C;;;;OAIG;IACG,KAAK;IAUX;;;;OAIG;IACG,OAAO;CASd"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,OAAO,EAEL,SAAS,EACT,sBAAsB,EAGtB,iBAAiB,EAGlB,MAAM,WAAW,CAAC;AAGnB;;;;;;;;;;GAUG;AACH,8BAAsB,UAAU;IAC9B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;gBAG/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,WAAW,EAAE,iBAAiB;IAMhC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IACvC,QAAQ,CAAC,KAAK,IAAI,IAAI;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,8BAAsB,SAAS,CAAC,QAAQ,SAAS,UAAU;IACzD;;;OAGG;IACH,KAAK,EAAE,eAAe,CAAC;IAEvB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,iBAAiB,CAAC;IAE5B;;OAEG;IACH,eAAe,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,CAAC,CAAC;IAE5D;;;OAGG;IACH,SAAS,EAAE,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAEpD;;OAEG;IACH,UAAU,EAAE,GAAG,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAEnD;;OAEG;IACH,WAAW,EAAE,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAE9C;;;;OAIG;gBACS,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB;IAUrD;;;OAGG;IACH,QAAQ,CAAC,8BAA8B,IAAI,IAAI;IAE/C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAElE;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,QAAQ;IAyBxB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,QAAQ;IAM3B;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,UAAU;IAIzB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,sBAAsB,GAAG,IAAI;IAmBlE;;;;OAIG;IACH,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI;IAgCtD;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAIxE;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAI3E;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IA4C5C;;;;OAIG;IACG,KAAK;IAUX;;;;OAIG;IACG,OAAO;CASd"}