@replit/river 0.1.5 → 0.1.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 +4 -1
- package/dist/__tests__/integration.test.js +20 -7
- package/dist/codec/json.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/router/client.js +1 -1
- package/dist/router/server.js +2 -1
- package/dist/router/server.util.js +4 -2
- package/dist/transport/stream.d.ts +10 -0
- package/dist/transport/stream.js +22 -0
- package/dist/transport/stream.test.d.ts +1 -0
- package/dist/transport/stream.test.js +26 -0
- package/dist/transport/util.d.ts +12 -0
- package/dist/transport/util.js +43 -0
- package/dist/transport/ws.test.js +1 -1
- package/package.json +5 -15
package/README.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# river - Streaming Remote Procedure Calls
|
|
2
2
|
|
|
3
3
|
It's like tRPC but...
|
|
4
|
+
|
|
4
5
|
- with JSON Schema Support
|
|
5
6
|
- with full-duplex streaming
|
|
6
7
|
- with support for service multiplexing
|
|
7
8
|
- over WebSockets
|
|
8
9
|
|
|
9
10
|
## Levels of abstraction
|
|
11
|
+
|
|
10
12
|
- Router
|
|
11
13
|
- Service
|
|
12
14
|
- Procedure
|
|
13
15
|
|
|
14
16
|
## TODO
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
- support broadcast
|
|
@@ -3,11 +3,14 @@ import { Type } from '@sinclair/typebox';
|
|
|
3
3
|
import { ServiceBuilder, serializeService } from '../router/builder';
|
|
4
4
|
import { reply } from '../transport/message';
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
|
|
6
|
-
import { createWebSocketServer, createWsTransports, onServerReady } from '../transport/
|
|
6
|
+
import { createWebSocketServer, createWsTransports, onServerReady, } from '../transport/util';
|
|
7
7
|
import { createServer } from '../router/server';
|
|
8
8
|
import { createClient } from '../router/client';
|
|
9
9
|
import { asClientRpc, asClientStream } from '../router/server.util';
|
|
10
|
-
export const EchoRequest = Type.Object({
|
|
10
|
+
export const EchoRequest = Type.Object({
|
|
11
|
+
msg: Type.String(),
|
|
12
|
+
ignore: Type.Boolean(),
|
|
13
|
+
});
|
|
11
14
|
export const EchoResponse = Type.Object({ response: Type.String() });
|
|
12
15
|
export const TestServiceConstructor = () => ServiceBuilder.create('test')
|
|
13
16
|
.initialState({
|
|
@@ -98,8 +101,12 @@ describe('server-side test', () => {
|
|
|
98
101
|
i.push({ msg: 'def', ignore: true });
|
|
99
102
|
i.push({ msg: 'ghi', ignore: false });
|
|
100
103
|
i.end();
|
|
101
|
-
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
102
|
-
|
|
104
|
+
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
105
|
+
response: 'abc',
|
|
106
|
+
});
|
|
107
|
+
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
108
|
+
response: 'ghi',
|
|
109
|
+
});
|
|
103
110
|
expect(o.readableLength).toBe(0);
|
|
104
111
|
});
|
|
105
112
|
});
|
|
@@ -122,7 +129,9 @@ describe('client <-> server integration test', () => {
|
|
|
122
129
|
const serviceDefs = { test: TestServiceConstructor() };
|
|
123
130
|
const server = await createServer(st, serviceDefs);
|
|
124
131
|
const client = createClient(ct);
|
|
125
|
-
await expect(client.test.add({ n: 3 })).resolves.toStrictEqual({
|
|
132
|
+
await expect(client.test.add({ n: 3 })).resolves.toStrictEqual({
|
|
133
|
+
result: 3,
|
|
134
|
+
});
|
|
126
135
|
});
|
|
127
136
|
test('stream', async () => {
|
|
128
137
|
const [ct, st] = await createWsTransports(port, wss);
|
|
@@ -134,8 +143,12 @@ describe('client <-> server integration test', () => {
|
|
|
134
143
|
i.push({ msg: 'def', ignore: true });
|
|
135
144
|
i.push({ msg: 'ghi', ignore: false });
|
|
136
145
|
i.end();
|
|
137
|
-
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
138
|
-
|
|
146
|
+
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
147
|
+
response: 'abc',
|
|
148
|
+
});
|
|
149
|
+
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
|
|
150
|
+
response: 'ghi',
|
|
151
|
+
});
|
|
139
152
|
close();
|
|
140
153
|
});
|
|
141
154
|
});
|
package/dist/codec/json.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Codec } from
|
|
1
|
+
import { Codec } from './types';
|
|
2
2
|
export declare const NaiveJsonCodec: Codec;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export type { Server } from './router/server';
|
|
|
8
8
|
export { Transport } from './transport/types';
|
|
9
9
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, TransportAckSchema, msg, payloadToTransportMessage, ack, reply, } from './transport/message';
|
|
10
10
|
export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, TransportMessageAck, } from './transport/message';
|
|
11
|
-
export {
|
|
11
|
+
export { StreamTransport } from './transport/stream';
|
|
12
12
|
export { WebSocketTransport } from './transport/ws';
|
|
13
|
-
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/
|
|
13
|
+
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/util';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,6 @@ export { createServer } from './router/server';
|
|
|
4
4
|
export { asClientRpc, asClientStream } from './router/server.util';
|
|
5
5
|
export { Transport } from './transport/types';
|
|
6
6
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, TransportAckSchema, msg, payloadToTransportMessage, ack, reply, } from './transport/message';
|
|
7
|
-
export {
|
|
7
|
+
export { StreamTransport } from './transport/stream';
|
|
8
8
|
export { WebSocketTransport } from './transport/ws';
|
|
9
|
-
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/
|
|
9
|
+
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/util';
|
package/dist/router/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pushable } from 'it-pushable';
|
|
2
2
|
import { msg } from '../transport/message';
|
|
3
|
-
import { waitForMessage } from '../transport/
|
|
3
|
+
import { waitForMessage } from '../transport/util';
|
|
4
4
|
const noop = () => { };
|
|
5
5
|
function _createRecursiveProxy(callback, path) {
|
|
6
6
|
const proxy = new Proxy(noop, {
|
package/dist/router/server.js
CHANGED
|
@@ -37,7 +37,8 @@ export async function createServer(transport, services) {
|
|
|
37
37
|
if (msg.procedureName in service.procedures) {
|
|
38
38
|
const procedure = service.procedures[msg.procedureName];
|
|
39
39
|
const inputMessage = msg;
|
|
40
|
-
if (procedure.type === 'rpc' &&
|
|
40
|
+
if (procedure.type === 'rpc' &&
|
|
41
|
+
Value.Check(procedure.input, inputMessage.payload)) {
|
|
41
42
|
// synchronous rpc
|
|
42
43
|
const response = await procedure.handler(service.state, inputMessage);
|
|
43
44
|
transport.send(response);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { payloadToTransportMessage } from '../transport/message';
|
|
1
|
+
import { payloadToTransportMessage, } from '../transport/message';
|
|
2
2
|
import { pushable } from 'it-pushable';
|
|
3
3
|
export function asClientRpc(state, proc) {
|
|
4
|
-
return (msg) => proc
|
|
4
|
+
return (msg) => proc
|
|
5
|
+
.handler(state, payloadToTransportMessage(msg))
|
|
6
|
+
.then((res) => res.payload);
|
|
5
7
|
}
|
|
6
8
|
export function asClientStream(state, proc) {
|
|
7
9
|
const i = pushable({ objectMode: true });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { OpaqueTransportMessage, TransportClientId } from './message';
|
|
3
|
+
import { Transport } from './types';
|
|
4
|
+
export declare class StreamTransport extends Transport {
|
|
5
|
+
input: NodeJS.ReadableStream;
|
|
6
|
+
output: NodeJS.WritableStream;
|
|
7
|
+
constructor(clientId: TransportClientId, input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream);
|
|
8
|
+
send(msg: OpaqueTransportMessage): string;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NaiveJsonCodec } from '../codec/json';
|
|
2
|
+
import { Transport } from './types';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
export class StreamTransport extends Transport {
|
|
5
|
+
input;
|
|
6
|
+
output;
|
|
7
|
+
constructor(clientId, input = process.stdin, output = process.stdout) {
|
|
8
|
+
super(NaiveJsonCodec, clientId);
|
|
9
|
+
this.input = input;
|
|
10
|
+
this.output = output;
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: this.input,
|
|
13
|
+
});
|
|
14
|
+
rl.on('line', (msg) => this.onMessage(msg));
|
|
15
|
+
}
|
|
16
|
+
send(msg) {
|
|
17
|
+
const id = msg.id;
|
|
18
|
+
this.output.write(this.codec.toStringBuf(msg) + '\n');
|
|
19
|
+
return id;
|
|
20
|
+
}
|
|
21
|
+
async close() { }
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import stream from 'node:stream';
|
|
3
|
+
import { StreamTransport } from './stream';
|
|
4
|
+
import { waitForMessage } from './util';
|
|
5
|
+
describe('sending and receiving across node streams works', () => {
|
|
6
|
+
test('basic send/receive', async () => {
|
|
7
|
+
const clientToServer = new stream.PassThrough();
|
|
8
|
+
const serverToClient = new stream.PassThrough();
|
|
9
|
+
const serverTransport = new StreamTransport('SERVER', clientToServer, serverToClient);
|
|
10
|
+
const clientTransport = new StreamTransport('client', serverToClient, clientToServer);
|
|
11
|
+
const msg = {
|
|
12
|
+
msg: 'cool',
|
|
13
|
+
test: 123,
|
|
14
|
+
};
|
|
15
|
+
const p = waitForMessage(serverTransport);
|
|
16
|
+
clientTransport.send({
|
|
17
|
+
id: '1',
|
|
18
|
+
from: 'client',
|
|
19
|
+
to: 'SERVER',
|
|
20
|
+
serviceName: 'test',
|
|
21
|
+
procedureName: 'test',
|
|
22
|
+
payload: msg,
|
|
23
|
+
});
|
|
24
|
+
await expect(p).resolves.toStrictEqual(msg);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import WebSocket from 'isomorphic-ws';
|
|
4
|
+
import { WebSocketServer } from 'ws';
|
|
5
|
+
import { Transport } from './types';
|
|
6
|
+
import { OpaqueTransportMessage } from './message';
|
|
7
|
+
export declare function createWebSocketServer(server: http.Server): Promise<WebSocket.Server<typeof WebSocket, typeof http.IncomingMessage>>;
|
|
8
|
+
export declare function onServerReady(server: http.Server, port: number): Promise<void>;
|
|
9
|
+
export declare function createWsTransports(port: number, wss: WebSocketServer): Promise<[Transport, Transport]>;
|
|
10
|
+
export declare function waitForSocketReady(socket: WebSocket): Promise<void>;
|
|
11
|
+
export declare function createWebSocketClient(port: number): Promise<WebSocket>;
|
|
12
|
+
export declare function waitForMessage(t: Transport, filter?: (msg: OpaqueTransportMessage) => boolean): Promise<unknown>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import WebSocket from 'isomorphic-ws';
|
|
2
|
+
import { WebSocketServer } from 'ws';
|
|
3
|
+
import { WebSocketTransport } from './ws';
|
|
4
|
+
export async function createWebSocketServer(server) {
|
|
5
|
+
return new WebSocketServer({ server });
|
|
6
|
+
}
|
|
7
|
+
export async function onServerReady(server, port) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
server.listen(port, resolve);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export async function createWsTransports(port, wss) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const clientSockPromise = createWebSocketClient(port);
|
|
15
|
+
wss.on('connection', async (serverSock) => {
|
|
16
|
+
resolve([
|
|
17
|
+
new WebSocketTransport(await clientSockPromise, 'client'),
|
|
18
|
+
new WebSocketTransport(serverSock, 'SERVER'),
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export async function waitForSocketReady(socket) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
socket.addEventListener('open', () => resolve());
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function createWebSocketClient(port) {
|
|
29
|
+
const client = new WebSocket(`ws://localhost:${port}`);
|
|
30
|
+
await waitForSocketReady(client);
|
|
31
|
+
return client;
|
|
32
|
+
}
|
|
33
|
+
export async function waitForMessage(t, filter) {
|
|
34
|
+
return new Promise((resolve, _reject) => {
|
|
35
|
+
function onMessage(msg) {
|
|
36
|
+
if (!filter || filter?.(msg)) {
|
|
37
|
+
resolve(msg.payload);
|
|
38
|
+
t.removeMessageListener(onMessage);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
t.addMessageListener(onMessage);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { WebSocketTransport } from './ws';
|
|
3
3
|
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
|
|
4
|
-
import { createWebSocketClient, createWebSocketServer, onServerReady, waitForMessage, } from './
|
|
4
|
+
import { createWebSocketClient, createWebSocketServer, onServerReady, waitForMessage, } from './util';
|
|
5
5
|
const port = 3000;
|
|
6
6
|
describe('sending and receiving across websockets works', () => {
|
|
7
7
|
const server = http.createServer();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replit/river",
|
|
3
3
|
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/ws": "^8.5.5",
|
|
20
|
-
"prettier": "^3.0.
|
|
20
|
+
"prettier": "^3.0.0",
|
|
21
21
|
"tsup": "^7.2.0",
|
|
22
22
|
"typescript": "^5.2.2",
|
|
23
23
|
"vitest": "^0.34.3"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"check": "tsc --noEmit",
|
|
26
|
+
"check": "tsc --noEmit && npx prettier . --check",
|
|
27
|
+
"format": "npx prettier . --write",
|
|
27
28
|
"build": "tsc",
|
|
28
29
|
"prepack": "npm run build",
|
|
29
30
|
"release": "npm publish --access public",
|
|
@@ -38,16 +39,5 @@
|
|
|
38
39
|
"jsonschema"
|
|
39
40
|
],
|
|
40
41
|
"author": "Jacky Zhao",
|
|
41
|
-
"license": "MIT"
|
|
42
|
-
"prettier": {
|
|
43
|
-
"printWidth": 100,
|
|
44
|
-
"tabWidth": 2,
|
|
45
|
-
"singleQuote": true,
|
|
46
|
-
"trailingComma": "all",
|
|
47
|
-
"bracketSpacing": true,
|
|
48
|
-
"semi": true,
|
|
49
|
-
"useTabs": false,
|
|
50
|
-
"parser": "typescript",
|
|
51
|
-
"arrowParens": "always"
|
|
52
|
-
}
|
|
42
|
+
"license": "MIT"
|
|
53
43
|
}
|