@replit/river 0.10.4 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -6
- package/dist/{chunk-UU2Z7LDR.js → chunk-5735JCTZ.js} +2 -5
- package/dist/{chunk-PC65ZFWJ.js → chunk-KRYKORQT.js} +1 -1
- package/dist/{chunk-RGMHF6PF.js → chunk-LRQGTUTI.js} +6 -8
- package/dist/{chunk-AJQU4AZG.js → chunk-MF3Z3IDF.js} +1 -3
- package/dist/{chunk-FWPZDOFL.js → chunk-SCULZ4KS.js} +18 -0
- package/dist/chunk-XWRKNZSC.js +80 -0
- package/dist/connection-2529fc14.d.ts +10 -0
- package/dist/connection-316d6e3a.d.ts +10 -0
- package/dist/router/index.cjs +18 -0
- package/dist/router/index.js +1 -1
- package/dist/transport/impls/stdio/stdio.cjs +78 -38
- package/dist/transport/impls/stdio/stdio.d.cts +4 -11
- package/dist/transport/impls/stdio/stdio.d.ts +4 -11
- package/dist/transport/impls/stdio/stdio.js +21 -36
- package/dist/transport/impls/unixsocket/client.cjs +506 -0
- package/dist/transport/impls/unixsocket/client.d.cts +16 -0
- package/dist/transport/impls/unixsocket/client.d.ts +16 -0
- package/dist/transport/impls/unixsocket/client.js +67 -0
- package/dist/transport/impls/unixsocket/server.cjs +510 -0
- package/dist/transport/impls/unixsocket/server.d.cts +18 -0
- package/dist/transport/impls/unixsocket/server.d.ts +18 -0
- package/dist/transport/impls/unixsocket/server.js +73 -0
- package/dist/transport/impls/ws/client.cjs +1 -6
- package/dist/transport/impls/ws/client.d.cts +0 -1
- package/dist/transport/impls/ws/client.d.ts +0 -1
- package/dist/transport/impls/ws/client.js +3 -3
- package/dist/transport/impls/ws/server.cjs +5 -9
- package/dist/transport/impls/ws/server.d.cts +0 -2
- package/dist/transport/impls/ws/server.d.ts +0 -2
- package/dist/transport/impls/ws/server.js +3 -3
- package/dist/transport/index.cjs +1 -3
- package/dist/transport/index.d.cts +6 -7
- package/dist/transport/index.d.ts +6 -7
- package/dist/transport/index.js +1 -1
- package/dist/util/testHelpers.cjs +24 -15
- package/dist/util/testHelpers.d.cts +6 -3
- package/dist/util/testHelpers.d.ts +6 -3
- package/dist/util/testHelpers.js +19 -8
- package/package.json +23 -14
package/README.md
CHANGED
|
@@ -1,15 +1,130 @@
|
|
|
1
1
|
# river - Streaming Remote Procedure Calls
|
|
2
2
|
|
|
3
|
-
It's like tRPC but
|
|
3
|
+
It's like tRPC/gRPC but with
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
- JSON Schema Support + run-time schema validation
|
|
6
|
+
- full-duplex streaming
|
|
7
|
+
- service multiplexing
|
|
8
|
+
- result types and error handling
|
|
9
|
+
- snappy DX (no code-generation)
|
|
10
|
+
- over any transport (WebSockets, stdio, Unix Domain Socket out of the box)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
10
13
|
|
|
11
14
|
To use River, you must be on least Typescript 5 with `"moduleResolution": "bundler"`.
|
|
12
15
|
|
|
16
|
+
```bash
|
|
17
|
+
npm i @replit/river @sinclair/typebox
|
|
18
|
+
|
|
19
|
+
# if you plan on using WebSocket for transport, also install
|
|
20
|
+
npm i ws isomorphic-ws
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Writing Services
|
|
24
|
+
|
|
25
|
+
### Concepts
|
|
26
|
+
|
|
27
|
+
- Router: a collection of services, namespaced by service name.
|
|
28
|
+
- Service: a collection of procedures with shared state.
|
|
29
|
+
- Procedure: a single procedure. A procedure declares its type, an input message type, an output message type, optionally an error type, and the associated handler. Valid types are:
|
|
30
|
+
- `rpc` whose handler has a signature of `Input -> Result<Output, Error>`.
|
|
31
|
+
- `upload` whose handler has a signature of `AsyncIterableIterator<Input> -> Result<Output, Error>`.
|
|
32
|
+
- `subscription` whose handler has a signature of `Input -> Pushable<Result<Output, Error>>`.
|
|
33
|
+
- `stream` whose handler has a signature of `AsyncIterableIterator<Input> -> Pushable<Result<Output, Error>>`.
|
|
34
|
+
- Transport: manages the lifecycle (creation/deletion) of connections and multiplexing read/writes from clients. Both the client and the server must be passed in a subclass of `Transport` to work.
|
|
35
|
+
- Codec: encodes messages between clients/servers before the transport sends it across the wire.
|
|
36
|
+
|
|
37
|
+
### A basic router
|
|
38
|
+
|
|
39
|
+
First, we create a service using the `ServiceBuilder`
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { ServiceBuilder, Ok, buildServiceDefs } from '@replit/river';
|
|
43
|
+
import { Type } from '@sinclair/typebox';
|
|
44
|
+
|
|
45
|
+
export const ExampleServiceConstructor = () =>
|
|
46
|
+
ServiceBuilder.create('example')
|
|
47
|
+
.initialState({
|
|
48
|
+
count: 0,
|
|
49
|
+
})
|
|
50
|
+
.defineProcedure('add', {
|
|
51
|
+
type: 'rpc',
|
|
52
|
+
input: Type.Object({ n: Type.Number() }),
|
|
53
|
+
output: Type.Object({ result: Type.Number() }),
|
|
54
|
+
errors: Type.Never(),
|
|
55
|
+
async handler(ctx, { n }) {
|
|
56
|
+
ctx.state.count += n;
|
|
57
|
+
return Ok({ result: ctx.state.count });
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
.finalize();
|
|
61
|
+
|
|
62
|
+
// expore a listing of all the services that we have
|
|
63
|
+
export const serviceDefs = buildServiceDefs([ExampleServiceConstructor()]);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then, we create the server
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import http from 'http';
|
|
70
|
+
import { WebSocketServer } from 'ws';
|
|
71
|
+
import { WebSocketServerTransport } from '@replit/river/transport/ws/server';
|
|
72
|
+
import { createServer } from '@replit/river';
|
|
73
|
+
|
|
74
|
+
// start websocket server on port 3000
|
|
75
|
+
const httpServer = http.createServer();
|
|
76
|
+
const port = 3000;
|
|
77
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
78
|
+
const transport = new WebSocketServerTransport(wss, 'SERVER');
|
|
79
|
+
|
|
80
|
+
export const server = createServer(transport, serviceDefs);
|
|
81
|
+
export type ServiceSurface = typeof server;
|
|
82
|
+
|
|
83
|
+
httpServer.listen(port);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In another file for the client (to create a separate entrypoint),
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import WebSocket from 'isomorphic-ws';
|
|
90
|
+
import { WebSocketClientTransport } from '@replit/river/transport/ws/client';
|
|
91
|
+
import { createClient } from '@replit/river';
|
|
92
|
+
|
|
93
|
+
const websocketUrl = `ws://localhost:3000`;
|
|
94
|
+
const transport = new WebSocketClientTransport(
|
|
95
|
+
async () => new WebSocket(websocketUrl),
|
|
96
|
+
'my-client-id',
|
|
97
|
+
'SERVER',
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const client = createClient<ServiceSurface>(transport, 'SERVER');
|
|
101
|
+
|
|
102
|
+
// we get full type safety on `client`
|
|
103
|
+
// client.<service name>.<procedure name>.<procedure type>()
|
|
104
|
+
// e.g.
|
|
105
|
+
const result = await client.example.add.rpc({ n: 3 });
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
const msg = result.payload;
|
|
108
|
+
console.log(msg.result); // 0 + 3 = 3
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
To add logging,
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { bindLogger, setLevel } from '@replit/river/logging';
|
|
116
|
+
|
|
117
|
+
bindLogger(console.log);
|
|
118
|
+
setLevel('info');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Further examples
|
|
122
|
+
|
|
123
|
+
We've also provided an end-to-end testing environment using Next.js, and a simple backend connected
|
|
124
|
+
with the WebSocket transport that you can [play with on Replit](https://replit.com/@jzhao-replit/riverbed).
|
|
125
|
+
|
|
126
|
+
You can find more service examples in the [E2E test fixtures](https://github.com/replit/river/blob/main/__tests__/fixtures/services.ts)
|
|
127
|
+
|
|
13
128
|
## Developing
|
|
14
129
|
|
|
15
130
|
[](https://replit.com/new/github/replit/river)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WebSocketConnection
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KRYKORQT.js";
|
|
4
4
|
import {
|
|
5
5
|
NaiveJsonCodec
|
|
6
6
|
} from "./chunk-R6H2BIMC.js";
|
|
7
7
|
import {
|
|
8
8
|
Transport
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MF3Z3IDF.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
12
|
} from "./chunk-SLUSVGQH.js";
|
|
@@ -39,9 +39,6 @@ var WebSocketClientTransport = class extends Transport {
|
|
|
39
39
|
this.serverId = serverId;
|
|
40
40
|
this.options = options;
|
|
41
41
|
this.reconnectPromises = /* @__PURE__ */ new Map();
|
|
42
|
-
this.setupConnectionStatusListeners();
|
|
43
|
-
}
|
|
44
|
-
setupConnectionStatusListeners() {
|
|
45
42
|
this.createNewConnection(this.serverId);
|
|
46
43
|
}
|
|
47
44
|
async createNewConnection(to, attempt = 0) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WebSocketConnection
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KRYKORQT.js";
|
|
4
4
|
import {
|
|
5
5
|
NaiveJsonCodec
|
|
6
6
|
} from "./chunk-R6H2BIMC.js";
|
|
7
7
|
import {
|
|
8
8
|
Transport
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MF3Z3IDF.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
12
|
} from "./chunk-SLUSVGQH.js";
|
|
@@ -17,13 +17,14 @@ var defaultOptions = {
|
|
|
17
17
|
};
|
|
18
18
|
var WebSocketServerTransport = class extends Transport {
|
|
19
19
|
wss;
|
|
20
|
-
clientId;
|
|
21
20
|
constructor(wss, clientId, providedOptions) {
|
|
22
21
|
const options = { ...defaultOptions, ...providedOptions };
|
|
23
22
|
super(options.codec, clientId);
|
|
24
23
|
this.wss = wss;
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
wss.on("listening", () => {
|
|
25
|
+
log?.info(`${this.clientId} -- server is listening`);
|
|
26
|
+
});
|
|
27
|
+
this.wss.on("connection", this.connectionHandler);
|
|
27
28
|
}
|
|
28
29
|
connectionHandler = (ws) => {
|
|
29
30
|
let conn = void 0;
|
|
@@ -46,9 +47,6 @@ var WebSocketServerTransport = class extends Transport {
|
|
|
46
47
|
);
|
|
47
48
|
};
|
|
48
49
|
};
|
|
49
|
-
setupConnectionStatusListeners() {
|
|
50
|
-
this.wss.on("connection", this.connectionHandler);
|
|
51
|
-
}
|
|
52
50
|
async createNewConnection(to) {
|
|
53
51
|
const err = `${this.clientId} -- failed to send msg to ${to}, client probably dropped`;
|
|
54
52
|
log?.warn(err);
|
|
@@ -81,6 +81,7 @@ var Transport = class {
|
|
|
81
81
|
eventDispatcher;
|
|
82
82
|
/**
|
|
83
83
|
* Creates a new Transport instance.
|
|
84
|
+
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
84
85
|
* @param codec The codec used to encode and decode messages.
|
|
85
86
|
* @param clientId The client ID of this transport.
|
|
86
87
|
*/
|
|
@@ -183,9 +184,6 @@ var Transport = class {
|
|
|
183
184
|
}
|
|
184
185
|
} else {
|
|
185
186
|
log?.info(`${this.clientId} -- received msg: ${JSON.stringify(msg)}`);
|
|
186
|
-
if (msg.to !== this.clientId) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
187
|
this.eventDispatcher.dispatchEvent("message", msg);
|
|
190
188
|
if (!isAck(msg.controlFlags)) {
|
|
191
189
|
const ackMsg = reply(msg, { ack: msg.id });
|
|
@@ -373,6 +373,9 @@ function _pushable(getNext, options) {
|
|
|
373
373
|
},
|
|
374
374
|
get readableLength() {
|
|
375
375
|
return _pushable2.readableLength;
|
|
376
|
+
},
|
|
377
|
+
onEmpty: (opts) => {
|
|
378
|
+
return _pushable2.onEmpty(opts);
|
|
376
379
|
}
|
|
377
380
|
};
|
|
378
381
|
return pushable2;
|
|
@@ -512,6 +515,12 @@ function handleRpc(transport, serverId, input, serviceName, procName) {
|
|
|
512
515
|
transport.removeEventListener("connectionStatus", onConnectionStatus);
|
|
513
516
|
}
|
|
514
517
|
function onMessage(msg2) {
|
|
518
|
+
if (msg2.streamId !== streamId) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (msg2.to !== transport.clientId) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
515
524
|
if (msg2.streamId === streamId) {
|
|
516
525
|
cleanup();
|
|
517
526
|
resolve(msg2.payload);
|
|
@@ -557,6 +566,9 @@ function handleStream(transport, serverId, init, serviceName, procName) {
|
|
|
557
566
|
if (msg2.streamId !== streamId) {
|
|
558
567
|
return;
|
|
559
568
|
}
|
|
569
|
+
if (msg2.to !== transport.clientId) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
560
572
|
if (isStreamClose(msg2.controlFlags)) {
|
|
561
573
|
cleanup();
|
|
562
574
|
} else {
|
|
@@ -603,6 +615,9 @@ function handleSubscribe(transport, serverId, input, serviceName, procName) {
|
|
|
603
615
|
if (msg2.streamId !== streamId) {
|
|
604
616
|
return;
|
|
605
617
|
}
|
|
618
|
+
if (msg2.to !== transport.clientId) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
606
621
|
if (isStreamClose(msg2.controlFlags)) {
|
|
607
622
|
cleanup();
|
|
608
623
|
} else {
|
|
@@ -677,6 +692,9 @@ function handleUpload(transport, serverId, input, serviceName, procName) {
|
|
|
677
692
|
transport.removeEventListener("connectionStatus", onConnectionStatus);
|
|
678
693
|
}
|
|
679
694
|
function onMessage(msg2) {
|
|
695
|
+
if (msg2.to !== transport.clientId) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
680
698
|
if (msg2.streamId === streamId) {
|
|
681
699
|
cleanup();
|
|
682
700
|
resolve(msg2.payload);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection
|
|
3
|
+
} from "./chunk-MF3Z3IDF.js";
|
|
4
|
+
|
|
5
|
+
// transport/transforms/delim.ts
|
|
6
|
+
import { Transform } from "node:stream";
|
|
7
|
+
var DelimiterParser = class extends Transform {
|
|
8
|
+
delimiter;
|
|
9
|
+
buffer;
|
|
10
|
+
maxBufferSizeBytes;
|
|
11
|
+
constructor({ delimiter, maxBufferSizeBytes, ...options }) {
|
|
12
|
+
super(options);
|
|
13
|
+
this.maxBufferSizeBytes = maxBufferSizeBytes;
|
|
14
|
+
this.delimiter = Buffer.from(delimiter);
|
|
15
|
+
this.buffer = Buffer.alloc(0);
|
|
16
|
+
}
|
|
17
|
+
// tldr; backpressure will be automatically applied for transform streams
|
|
18
|
+
// but it relies on both the input/output streams connected on either end having
|
|
19
|
+
// implemented backpressure properly
|
|
20
|
+
// see: https://nodejs.org/en/guides/backpressuring-in-streams#lifecycle-of-pipe
|
|
21
|
+
_transform(chunk, _encoding, cb) {
|
|
22
|
+
let data = Buffer.concat([this.buffer, chunk]);
|
|
23
|
+
let position;
|
|
24
|
+
while ((position = data.indexOf(this.delimiter)) !== -1) {
|
|
25
|
+
this.push(data.subarray(0, position));
|
|
26
|
+
data = data.subarray(position + this.delimiter.length);
|
|
27
|
+
}
|
|
28
|
+
if (data.byteLength > this.maxBufferSizeBytes) {
|
|
29
|
+
const err = new Error(
|
|
30
|
+
`buffer overflow: ${data.byteLength}B > ${this.maxBufferSizeBytes}B`
|
|
31
|
+
);
|
|
32
|
+
this.emit("error", err);
|
|
33
|
+
return cb(err);
|
|
34
|
+
}
|
|
35
|
+
this.buffer = data;
|
|
36
|
+
cb();
|
|
37
|
+
}
|
|
38
|
+
_flush(cb) {
|
|
39
|
+
if (this.buffer.length) {
|
|
40
|
+
this.push(this.buffer);
|
|
41
|
+
}
|
|
42
|
+
this.buffer = Buffer.alloc(0);
|
|
43
|
+
cb();
|
|
44
|
+
}
|
|
45
|
+
_destroy(error, callback) {
|
|
46
|
+
this.buffer = Buffer.alloc(0);
|
|
47
|
+
super._destroy(error, callback);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var defaultDelimiter = Buffer.from("\n");
|
|
51
|
+
function createDelimitedStream(options) {
|
|
52
|
+
return new DelimiterParser({
|
|
53
|
+
delimiter: options?.delimiter ?? defaultDelimiter,
|
|
54
|
+
maxBufferSizeBytes: options?.maxBufferSizeBytes ?? 16 * 1024 * 1024
|
|
55
|
+
// 16MB
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// transport/impls/stdio/connection.ts
|
|
60
|
+
var StreamConnection = class extends Connection {
|
|
61
|
+
output;
|
|
62
|
+
constructor(transport, connectedTo, output) {
|
|
63
|
+
super(transport, connectedTo);
|
|
64
|
+
this.output = output;
|
|
65
|
+
}
|
|
66
|
+
send(payload) {
|
|
67
|
+
if (!this.output.writable) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return this.output.write(Buffer.concat([payload, defaultDelimiter]));
|
|
71
|
+
}
|
|
72
|
+
async close() {
|
|
73
|
+
this.output.end();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
createDelimitedStream,
|
|
79
|
+
StreamConnection
|
|
80
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Connection, Transport, TransportClientId } from './transport/index.cjs';
|
|
2
|
+
|
|
3
|
+
declare class StreamConnection extends Connection {
|
|
4
|
+
output: NodeJS.WritableStream;
|
|
5
|
+
constructor(transport: Transport<StreamConnection>, connectedTo: TransportClientId, output: NodeJS.WritableStream);
|
|
6
|
+
send(payload: Uint8Array): boolean;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { StreamConnection as S };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Connection, Transport, TransportClientId } from './transport/index.js';
|
|
2
|
+
|
|
3
|
+
declare class StreamConnection extends Connection {
|
|
4
|
+
output: NodeJS.WritableStream;
|
|
5
|
+
constructor(transport: Transport<StreamConnection>, connectedTo: TransportClientId, output: NodeJS.WritableStream);
|
|
6
|
+
send(payload: Uint8Array): boolean;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { StreamConnection as S };
|
package/dist/router/index.cjs
CHANGED
|
@@ -395,6 +395,9 @@ function _pushable(getNext, options) {
|
|
|
395
395
|
},
|
|
396
396
|
get readableLength() {
|
|
397
397
|
return _pushable2.readableLength;
|
|
398
|
+
},
|
|
399
|
+
onEmpty: (opts) => {
|
|
400
|
+
return _pushable2.onEmpty(opts);
|
|
398
401
|
}
|
|
399
402
|
};
|
|
400
403
|
return pushable2;
|
|
@@ -592,6 +595,12 @@ function handleRpc(transport, serverId, input, serviceName, procName) {
|
|
|
592
595
|
transport.removeEventListener("connectionStatus", onConnectionStatus);
|
|
593
596
|
}
|
|
594
597
|
function onMessage(msg2) {
|
|
598
|
+
if (msg2.streamId !== streamId) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (msg2.to !== transport.clientId) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
595
604
|
if (msg2.streamId === streamId) {
|
|
596
605
|
cleanup();
|
|
597
606
|
resolve(msg2.payload);
|
|
@@ -637,6 +646,9 @@ function handleStream(transport, serverId, init, serviceName, procName) {
|
|
|
637
646
|
if (msg2.streamId !== streamId) {
|
|
638
647
|
return;
|
|
639
648
|
}
|
|
649
|
+
if (msg2.to !== transport.clientId) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
640
652
|
if (isStreamClose(msg2.controlFlags)) {
|
|
641
653
|
cleanup();
|
|
642
654
|
} else {
|
|
@@ -683,6 +695,9 @@ function handleSubscribe(transport, serverId, input, serviceName, procName) {
|
|
|
683
695
|
if (msg2.streamId !== streamId) {
|
|
684
696
|
return;
|
|
685
697
|
}
|
|
698
|
+
if (msg2.to !== transport.clientId) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
686
701
|
if (isStreamClose(msg2.controlFlags)) {
|
|
687
702
|
cleanup();
|
|
688
703
|
} else {
|
|
@@ -757,6 +772,9 @@ function handleUpload(transport, serverId, input, serviceName, procName) {
|
|
|
757
772
|
transport.removeEventListener("connectionStatus", onConnectionStatus);
|
|
758
773
|
}
|
|
759
774
|
function onMessage(msg2) {
|
|
775
|
+
if (msg2.to !== transport.clientId) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
760
778
|
if (msg2.streamId === streamId) {
|
|
761
779
|
cleanup();
|
|
762
780
|
resolve(msg2.payload);
|
package/dist/router/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,20 +15,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// transport/impls/stdio/stdio.ts
|
|
31
21
|
var stdio_exports = {};
|
|
32
22
|
__export(stdio_exports, {
|
|
33
|
-
StdioConnection: () => StdioConnection,
|
|
34
23
|
StdioTransport: () => StdioTransport
|
|
35
24
|
});
|
|
36
25
|
module.exports = __toCommonJS(stdio_exports);
|
|
@@ -84,6 +73,60 @@ var NaiveJsonCodec = {
|
|
|
84
73
|
// logging/index.ts
|
|
85
74
|
var log;
|
|
86
75
|
|
|
76
|
+
// transport/transforms/delim.ts
|
|
77
|
+
var import_node_stream = require("stream");
|
|
78
|
+
var DelimiterParser = class extends import_node_stream.Transform {
|
|
79
|
+
delimiter;
|
|
80
|
+
buffer;
|
|
81
|
+
maxBufferSizeBytes;
|
|
82
|
+
constructor({ delimiter, maxBufferSizeBytes, ...options }) {
|
|
83
|
+
super(options);
|
|
84
|
+
this.maxBufferSizeBytes = maxBufferSizeBytes;
|
|
85
|
+
this.delimiter = Buffer.from(delimiter);
|
|
86
|
+
this.buffer = Buffer.alloc(0);
|
|
87
|
+
}
|
|
88
|
+
// tldr; backpressure will be automatically applied for transform streams
|
|
89
|
+
// but it relies on both the input/output streams connected on either end having
|
|
90
|
+
// implemented backpressure properly
|
|
91
|
+
// see: https://nodejs.org/en/guides/backpressuring-in-streams#lifecycle-of-pipe
|
|
92
|
+
_transform(chunk, _encoding, cb) {
|
|
93
|
+
let data = Buffer.concat([this.buffer, chunk]);
|
|
94
|
+
let position;
|
|
95
|
+
while ((position = data.indexOf(this.delimiter)) !== -1) {
|
|
96
|
+
this.push(data.subarray(0, position));
|
|
97
|
+
data = data.subarray(position + this.delimiter.length);
|
|
98
|
+
}
|
|
99
|
+
if (data.byteLength > this.maxBufferSizeBytes) {
|
|
100
|
+
const err = new Error(
|
|
101
|
+
`buffer overflow: ${data.byteLength}B > ${this.maxBufferSizeBytes}B`
|
|
102
|
+
);
|
|
103
|
+
this.emit("error", err);
|
|
104
|
+
return cb(err);
|
|
105
|
+
}
|
|
106
|
+
this.buffer = data;
|
|
107
|
+
cb();
|
|
108
|
+
}
|
|
109
|
+
_flush(cb) {
|
|
110
|
+
if (this.buffer.length) {
|
|
111
|
+
this.push(this.buffer);
|
|
112
|
+
}
|
|
113
|
+
this.buffer = Buffer.alloc(0);
|
|
114
|
+
cb();
|
|
115
|
+
}
|
|
116
|
+
_destroy(error, callback) {
|
|
117
|
+
this.buffer = Buffer.alloc(0);
|
|
118
|
+
super._destroy(error, callback);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var defaultDelimiter = Buffer.from("\n");
|
|
122
|
+
function createDelimitedStream(options) {
|
|
123
|
+
return new DelimiterParser({
|
|
124
|
+
delimiter: options?.delimiter ?? defaultDelimiter,
|
|
125
|
+
maxBufferSizeBytes: options?.maxBufferSizeBytes ?? 16 * 1024 * 1024
|
|
126
|
+
// 16MB
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
87
130
|
// transport/transport.ts
|
|
88
131
|
var import_value = require("@sinclair/typebox/value");
|
|
89
132
|
|
|
@@ -195,6 +238,7 @@ var Transport = class {
|
|
|
195
238
|
eventDispatcher;
|
|
196
239
|
/**
|
|
197
240
|
* Creates a new Transport instance.
|
|
241
|
+
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
198
242
|
* @param codec The codec used to encode and decode messages.
|
|
199
243
|
* @param clientId The client ID of this transport.
|
|
200
244
|
*/
|
|
@@ -297,9 +341,6 @@ var Transport = class {
|
|
|
297
341
|
}
|
|
298
342
|
} else {
|
|
299
343
|
log?.info(`${this.clientId} -- received msg: ${JSON.stringify(msg)}`);
|
|
300
|
-
if (msg.to !== this.clientId) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
344
|
this.eventDispatcher.dispatchEvent("message", msg);
|
|
304
345
|
if (!isAck(msg.controlFlags)) {
|
|
305
346
|
const ackMsg = reply(msg, { ack: msg.id });
|
|
@@ -392,30 +433,29 @@ var Transport = class {
|
|
|
392
433
|
}
|
|
393
434
|
};
|
|
394
435
|
|
|
395
|
-
// transport/impls/stdio/
|
|
396
|
-
var
|
|
397
|
-
var newlineBuff = new TextEncoder().encode("\n");
|
|
398
|
-
var StdioConnection = class extends Connection {
|
|
436
|
+
// transport/impls/stdio/connection.ts
|
|
437
|
+
var StreamConnection = class extends Connection {
|
|
399
438
|
output;
|
|
400
439
|
constructor(transport, connectedTo, output) {
|
|
401
440
|
super(transport, connectedTo);
|
|
402
441
|
this.output = output;
|
|
403
442
|
}
|
|
404
443
|
send(payload) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return this.output.write(
|
|
444
|
+
if (!this.output.writable) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
return this.output.write(Buffer.concat([payload, defaultDelimiter]));
|
|
409
448
|
}
|
|
410
449
|
async close() {
|
|
411
450
|
this.output.end();
|
|
412
451
|
}
|
|
413
452
|
};
|
|
453
|
+
|
|
454
|
+
// transport/impls/stdio/stdio.ts
|
|
414
455
|
var defaultOptions = {
|
|
415
456
|
codec: NaiveJsonCodec
|
|
416
457
|
};
|
|
417
458
|
var StdioTransport = class extends Transport {
|
|
418
|
-
clientId;
|
|
419
459
|
input = process.stdin;
|
|
420
460
|
output = process.stdout;
|
|
421
461
|
/**
|
|
@@ -427,29 +467,30 @@ var StdioTransport = class extends Transport {
|
|
|
427
467
|
constructor(clientId, input = process.stdin, output = process.stdout, providedOptions) {
|
|
428
468
|
const options = { ...defaultOptions, ...providedOptions };
|
|
429
469
|
super(options.codec, clientId);
|
|
430
|
-
|
|
431
|
-
this.input = input;
|
|
470
|
+
const delimStream = createDelimitedStream();
|
|
471
|
+
this.input = input.pipe(delimStream);
|
|
432
472
|
this.output = output;
|
|
433
|
-
this.setupConnectionStatusListeners();
|
|
434
|
-
}
|
|
435
|
-
setupConnectionStatusListeners() {
|
|
436
473
|
let conn = void 0;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
});
|
|
440
|
-
const encoder2 = new TextEncoder();
|
|
441
|
-
rl.on("line", (msg) => {
|
|
442
|
-
const parsedMsg = this.parseMsg(encoder2.encode(msg));
|
|
474
|
+
this.input.on("data", (msg) => {
|
|
475
|
+
const parsedMsg = this.parseMsg(msg);
|
|
443
476
|
if (parsedMsg && !this.connections.has(parsedMsg.from)) {
|
|
444
|
-
conn = new
|
|
477
|
+
conn = new StreamConnection(this, parsedMsg.from, this.output);
|
|
445
478
|
this.onConnect(conn);
|
|
446
479
|
}
|
|
447
480
|
this.handleMsg(parsedMsg);
|
|
448
481
|
});
|
|
449
|
-
|
|
482
|
+
const cleanup = () => {
|
|
483
|
+
delimStream.destroy();
|
|
450
484
|
if (conn) {
|
|
451
485
|
this.onDisconnect(conn);
|
|
452
486
|
}
|
|
487
|
+
};
|
|
488
|
+
this.input.on("close", cleanup);
|
|
489
|
+
this.input.on("error", (err) => {
|
|
490
|
+
log?.warn(
|
|
491
|
+
`${this.clientId} -- stdio error in connection to ${conn?.connectedTo ?? "unknown"}: ${err}`
|
|
492
|
+
);
|
|
493
|
+
cleanup();
|
|
453
494
|
});
|
|
454
495
|
}
|
|
455
496
|
async createNewConnection(to) {
|
|
@@ -457,12 +498,11 @@ var StdioTransport = class extends Transport {
|
|
|
457
498
|
throw new Error("cant reopen a destroyed connection");
|
|
458
499
|
}
|
|
459
500
|
log?.info(`${this.clientId} -- establishing a new stream to ${to}`);
|
|
460
|
-
const conn = new
|
|
501
|
+
const conn = new StreamConnection(this, to, this.output);
|
|
461
502
|
this.onConnect(conn);
|
|
462
503
|
}
|
|
463
504
|
};
|
|
464
505
|
// Annotate the CommonJS export names for ESM import in node:
|
|
465
506
|
0 && (module.exports = {
|
|
466
|
-
StdioConnection,
|
|
467
507
|
StdioTransport
|
|
468
508
|
});
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { C as Codec } from '../../../types-3e5768ec.js';
|
|
2
|
-
import {
|
|
2
|
+
import { Transport, TransportClientId } from '../../index.cjs';
|
|
3
|
+
import { S as StreamConnection } from '../../../connection-2529fc14.js';
|
|
3
4
|
import '@sinclair/typebox';
|
|
4
5
|
|
|
5
|
-
declare class StdioConnection extends Connection {
|
|
6
|
-
output: NodeJS.WritableStream;
|
|
7
|
-
constructor(transport: Transport<StdioConnection>, connectedTo: TransportClientId, output: NodeJS.WritableStream);
|
|
8
|
-
send(payload: Uint8Array): boolean;
|
|
9
|
-
close(): Promise<void>;
|
|
10
|
-
}
|
|
11
6
|
interface Options {
|
|
12
7
|
codec: Codec;
|
|
13
8
|
}
|
|
@@ -15,8 +10,7 @@ interface Options {
|
|
|
15
10
|
* A transport implementation that uses standard input and output streams.
|
|
16
11
|
* @extends Transport
|
|
17
12
|
*/
|
|
18
|
-
declare class StdioTransport extends Transport<
|
|
19
|
-
clientId: TransportClientId;
|
|
13
|
+
declare class StdioTransport extends Transport<StreamConnection> {
|
|
20
14
|
input: NodeJS.ReadableStream;
|
|
21
15
|
output: NodeJS.WritableStream;
|
|
22
16
|
/**
|
|
@@ -26,8 +20,7 @@ declare class StdioTransport extends Transport<StdioConnection> {
|
|
|
26
20
|
* @param output - The writable stream to use as output. Defaults to process.stdout.
|
|
27
21
|
*/
|
|
28
22
|
constructor(clientId: TransportClientId, input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream, providedOptions?: Partial<Options>);
|
|
29
|
-
setupConnectionStatusListeners(): void;
|
|
30
23
|
createNewConnection(to: TransportClientId): Promise<void>;
|
|
31
24
|
}
|
|
32
25
|
|
|
33
|
-
export {
|
|
26
|
+
export { StdioTransport };
|