Skip to content

Commit f7594a5

Browse files
committed
feat: 🎸 add code
1 parent f557906 commit f7594a5

File tree

189 files changed

+15511
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+15511
-0
lines changed

src/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Reactive-RPC
2+
3+
Implements [Reactive-RPC](https://onp4.com/@vadim/p/qgzwgi42cz) protocol.
4+

src/__demos__/server.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// npx ts-node src/reactive-rpc/__demos__/server.ts
2+
3+
import {App} from 'uWebSockets.js';
4+
import {createCaller} from '../common/rpc/__tests__/sample-api';
5+
import {RpcApp} from '../server/uws/RpcApp';
6+
7+
const app = new RpcApp({
8+
uws: App({}),
9+
caller: createCaller(),
10+
});
11+
12+
app.startWithDefaults();

src/__demos__/ws.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// npx ts-node src/reactive-rpc/__demos__/ws.ts
2+
3+
import {createCaller} from '../common/rpc/__tests__/sample-api';
4+
import {RpcServer} from '../server/http1/RpcServer';
5+
6+
const server = RpcServer.startWithDefaults({
7+
port: 3000,
8+
caller: createCaller(),
9+
logger: console,
10+
});
11+
12+
// tslint:disable-next-line no-console
13+
console.log(server + '');

src/__tests__/e2e/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Reactive-RPC E2E tests
2+
3+
This folder contains E2E tests for Reactive-RPC. You can run the tests with a
4+
single command:
5+
6+
```
7+
yarn test:reactive-rpc
8+
```
9+
10+
Or you can start the server in one terminal window:
11+
12+
```
13+
yarn demo:reactive-rpc:server
14+
```
15+
16+
And run the test cases in another terminal window:
17+
18+
```
19+
yarn test:reactive-rpc:jest
20+
```
21+
22+
To run a specific test suite use this command:
23+
24+
```
25+
TEST_E2E=1 npx jest --no-cache src/reactive-rpc/__tests__/e2e/uws/ws/RpcPersistentClient.spec.ts
26+
```

src/__tests__/e2e/run.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {spawn} from 'child_process';
2+
import {Defer} from '../../util/Defer';
3+
4+
const startServer = async () => {
5+
const started = new Defer<void>();
6+
const exitCode = new Defer<number>();
7+
const cp = spawn('yarn', ['demo:reactive-rpc:server'], {
8+
shell: true,
9+
});
10+
process.on('exit', (code) => {
11+
cp.kill();
12+
});
13+
cp.stdout.on('data', (data) => {
14+
const line = String(data);
15+
if (line.indexOf('SERVER_STARTED') > -1) started.resolve();
16+
process.stderr.write('[server] ' + line);
17+
});
18+
cp.stderr.on('data', (data) => {
19+
// tslint:disable-next-line no-console
20+
console.error('Could not start server');
21+
started.reject(data);
22+
process.stderr.write('ERROR: [server] ' + String(data));
23+
});
24+
cp.on('close', (code) => {
25+
exitCode.resolve(code || 0);
26+
process.stdout.write('[server] ' + `process exited with code ${code}\n`);
27+
});
28+
return {
29+
cp,
30+
started: started.promise,
31+
exitCode: exitCode.promise,
32+
};
33+
};
34+
35+
const runTests = async () => {
36+
const exitCode = new Defer<number>();
37+
const cp = spawn('yarn', ['test:reactive-rpc:jest'], {
38+
env: {
39+
...process.env,
40+
TEST_E2E: '1',
41+
},
42+
stdio: 'inherit',
43+
});
44+
process.on('exit', (code) => {
45+
cp.kill();
46+
});
47+
cp.on('close', (code) => {
48+
exitCode.resolve(code || 0);
49+
process.stdout.write('[jest] ' + `process exited with code ${code}\n`);
50+
});
51+
return {
52+
cp,
53+
exitCode: exitCode.promise,
54+
};
55+
};
56+
57+
(async () => {
58+
try {
59+
const server = await startServer();
60+
await server.started;
61+
let exitCode = 0;
62+
const jest = await runTests();
63+
exitCode = await jest.exitCode;
64+
if (exitCode !== 0) throw exitCode;
65+
process.exit(exitCode);
66+
} catch (error) {
67+
// tslint:disable-next-line no-console
68+
console.error(error);
69+
process.exit(1);
70+
}
71+
})();
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import {ApiTestSetup, runApiTests} from '../../../../common/rpc/__tests__/runApiTests';
6+
import {RpcCodecs} from '../../../../common/codec/RpcCodecs';
7+
import {RpcMessageCodecs} from '../../../../common/codec/RpcMessageCodecs';
8+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
9+
import {Codecs} from '@jsonjoy.com/json-pack/lib/codecs/Codecs';
10+
import {RpcMessageCodec} from '../../../../common/codec/types';
11+
import {JsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/types';
12+
import {FetchRpcClient} from '../../../../common/rpc/client/FetchRpcClient';
13+
14+
if (process.env.TEST_E2E) {
15+
const codecs = new RpcCodecs(new Codecs(new Writer()), new RpcMessageCodecs());
16+
const {binary, compact, jsonRpc2} = codecs.messages;
17+
const {json, cbor, msgpack} = codecs.value;
18+
const cases: [specifier: string, protocol: RpcMessageCodec, req: JsonValueCodec, res: JsonValueCodec][] = [
19+
['rpc.rx.compact.json', compact, json, json],
20+
['rpc.rx.compact.cbor', compact, cbor, cbor],
21+
['rpc.rx.compact.msgpack', compact, msgpack, msgpack],
22+
['rpc.rx.compact.json-cbor', compact, json, cbor],
23+
['rpc.rx.compact.json-msgpack', compact, json, msgpack],
24+
['rpc.rx.compact.cbor-json', compact, cbor, json],
25+
['rpc.rx.compact.cbor-msgpack', compact, cbor, msgpack],
26+
['rpc.rx.compact.msgpack-json', compact, msgpack, json],
27+
['rpc.rx.compact.msgpack-cbor', compact, msgpack, cbor],
28+
29+
['rpc.rx.binary.cbor', binary, cbor, cbor],
30+
['rpc.rx.binary.msgpack', binary, msgpack, msgpack],
31+
['rpc.rx.binary.json', binary, json, json],
32+
['rpc.rx.binary.json-cbor', binary, json, cbor],
33+
['rpc.rx.binary.json-msgpack', binary, json, msgpack],
34+
['rpc.rx.binary.cbor-json', binary, cbor, json],
35+
['rpc.rx.binary.cbor-msgpack', binary, cbor, msgpack],
36+
['rpc.rx.binary.msgpack-json', binary, msgpack, json],
37+
['rpc.rx.binary.msgpack-cbor', binary, msgpack, cbor],
38+
39+
['rpc.json2.verbose.json', jsonRpc2, json, json],
40+
['rpc.json2.verbose.cbor', jsonRpc2, cbor, cbor],
41+
['rpc.json2.verbose.msgpack', jsonRpc2, msgpack, msgpack],
42+
['rpc.json2.verbose.json-cbor', jsonRpc2, json, cbor],
43+
['rpc.json2.verbose.json-msgpack', jsonRpc2, json, msgpack],
44+
['rpc.json2.verbose.cbor-json', jsonRpc2, cbor, json],
45+
['rpc.json2.verbose.cbor-msgpack', jsonRpc2, cbor, msgpack],
46+
['rpc.json2.verbose.msgpack-json', jsonRpc2, msgpack, json],
47+
['rpc.json2.verbose.msgpack-cbor', jsonRpc2, msgpack, cbor],
48+
];
49+
50+
for (const [protocolSpecifier, msgCodec, reqCodec, resCodec] of cases) {
51+
const setup: ApiTestSetup = async () => {
52+
const port = +(process.env.PORT || 9999);
53+
const url = `http://localhost:${port}/rpc`;
54+
const client = new FetchRpcClient({
55+
url,
56+
msgCodec,
57+
reqCodec,
58+
resCodec,
59+
});
60+
return {client};
61+
};
62+
describe(`Content-Type: application/x.${protocolSpecifier}`, () => {
63+
runApiTests(setup, {staticOnly: true});
64+
});
65+
}
66+
} else {
67+
test.skip('set TEST_E2E=1 env var to run this test suite', () => {});
68+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import {ApiTestSetup, runApiTests} from '../../../../common/rpc/__tests__/runApiTests';
6+
import {StreamingRpcClient} from '../../../../common';
7+
import {RpcCodecs} from '../../../../common/codec/RpcCodecs';
8+
import {RpcMessageCodecs} from '../../../../common/codec/RpcMessageCodecs';
9+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
10+
import {Codecs} from '@jsonjoy.com/json-pack/lib/codecs/Codecs';
11+
import {RpcMessageCodec} from '../../../../common/codec/types';
12+
import {JsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/types';
13+
14+
if (process.env.TEST_E2E) {
15+
const codecs = new RpcCodecs(new Codecs(new Writer()), new RpcMessageCodecs());
16+
const {binary, compact, jsonRpc2} = codecs.messages;
17+
const {json, cbor, msgpack} = codecs.value;
18+
const cases: [specifier: string, protocol: RpcMessageCodec, req: JsonValueCodec, res: JsonValueCodec][] = [
19+
['rpc.rx.compact.json', compact, json, json],
20+
['rpc.rx.compact.cbor', compact, cbor, cbor],
21+
['rpc.rx.compact.msgpack', compact, msgpack, msgpack],
22+
['rpc.rx.compact.json-cbor', compact, json, cbor],
23+
['rpc.rx.compact.json-msgpack', compact, json, msgpack],
24+
['rpc.rx.compact.cbor-json', compact, cbor, json],
25+
['rpc.rx.compact.cbor-msgpack', compact, cbor, msgpack],
26+
['rpc.rx.compact.msgpack-json', compact, msgpack, json],
27+
['rpc.rx.compact.msgpack-cbor', compact, msgpack, cbor],
28+
29+
['rpc.rx.binary.cbor', binary, cbor, cbor],
30+
['rpc.rx.binary.msgpack', binary, msgpack, msgpack],
31+
['rpc.rx.binary.json', binary, json, json],
32+
['rpc.rx.binary.json-cbor', binary, json, cbor],
33+
['rpc.rx.binary.json-msgpack', binary, json, msgpack],
34+
['rpc.rx.binary.cbor-json', binary, cbor, json],
35+
['rpc.rx.binary.cbor-msgpack', binary, cbor, msgpack],
36+
['rpc.rx.binary.msgpack-json', binary, msgpack, json],
37+
['rpc.rx.binary.msgpack-cbor', binary, msgpack, cbor],
38+
39+
['rpc.json2.verbose.json', jsonRpc2, json, json],
40+
['rpc.json2.verbose.cbor', jsonRpc2, cbor, cbor],
41+
['rpc.json2.verbose.msgpack', jsonRpc2, msgpack, msgpack],
42+
['rpc.json2.verbose.json-cbor', jsonRpc2, json, cbor],
43+
['rpc.json2.verbose.json-msgpack', jsonRpc2, json, msgpack],
44+
['rpc.json2.verbose.cbor-json', jsonRpc2, cbor, json],
45+
['rpc.json2.verbose.cbor-msgpack', jsonRpc2, cbor, msgpack],
46+
['rpc.json2.verbose.msgpack-json', jsonRpc2, msgpack, json],
47+
['rpc.json2.verbose.msgpack-cbor', jsonRpc2, msgpack, cbor],
48+
];
49+
50+
for (const [protocolSpecifier, msgCodec, reqCodec, resCodec] of cases) {
51+
const contentType = 'application/x.' + protocolSpecifier;
52+
const setup: ApiTestSetup = async () => {
53+
const client = new StreamingRpcClient({
54+
send: async (messages) => {
55+
const port = +(process.env.PORT || 9999);
56+
const url = `http://localhost:${port}/rpc`;
57+
reqCodec.encoder.writer.reset();
58+
msgCodec.encodeBatch(reqCodec, messages);
59+
const body = reqCodec.encoder.writer.flush();
60+
try {
61+
const response = await fetch(url, {
62+
method: 'POST',
63+
headers: {
64+
'Content-Type': contentType,
65+
},
66+
body,
67+
});
68+
const buffer = await response.arrayBuffer();
69+
const data = new Uint8Array(buffer);
70+
const responseMessages = msgCodec.decodeBatch(resCodec, data);
71+
client.onMessages(responseMessages as any);
72+
} catch (err) {
73+
// tslint:disable-next-line:no-console
74+
console.error(err);
75+
}
76+
},
77+
});
78+
return {client};
79+
};
80+
describe(`Content-Type: ${contentType}`, () => {
81+
runApiTests(setup, {staticOnly: true});
82+
});
83+
}
84+
} else {
85+
test.skip('set TEST_E2E=1 env var to run this test suite', () => {});
86+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import {ApiTestSetup, runApiTests} from '../../../../common/rpc/__tests__/runApiTests';
6+
import WebSocket from 'ws';
7+
import {RpcCodecs} from '../../../../common/codec/RpcCodecs';
8+
import {RpcMessageCodecs} from '../../../../common/codec/RpcMessageCodecs';
9+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
10+
import {Codecs} from '@jsonjoy.com/json-pack/lib/codecs/Codecs';
11+
import {RpcMessageCodec} from '../../../../common/codec/types';
12+
import {JsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/types';
13+
import {RpcCodec} from '../../../../common/codec/RpcCodec';
14+
import {RpcPersistentClient, WebSocketChannel} from '../../../../common';
15+
16+
if (process.env.TEST_E2E) {
17+
const codecs = new RpcCodecs(new Codecs(new Writer()), new RpcMessageCodecs());
18+
const {binary, compact, jsonRpc2} = codecs.messages;
19+
const {json, cbor, msgpack} = codecs.value;
20+
const cases: [specifier: string, protocol: RpcMessageCodec, req: JsonValueCodec, res: JsonValueCodec][] = [
21+
['rpc.rx.compact.json', compact, json, json],
22+
['rpc.rx.compact.cbor', compact, cbor, cbor],
23+
['rpc.rx.compact.msgpack', compact, msgpack, msgpack],
24+
['rpc.rx.compact.json-cbor', compact, json, cbor],
25+
['rpc.rx.compact.json-msgpack', compact, json, msgpack],
26+
['rpc.rx.compact.cbor-json', compact, cbor, json],
27+
['rpc.rx.compact.cbor-msgpack', compact, cbor, msgpack],
28+
['rpc.rx.compact.msgpack-json', compact, msgpack, json],
29+
['rpc.rx.compact.msgpack-cbor', compact, msgpack, cbor],
30+
31+
['rpc.rx.binary.cbor', binary, cbor, cbor],
32+
['rpc.rx.binary.msgpack', binary, msgpack, msgpack],
33+
['rpc.rx.binary.json', binary, json, json],
34+
['rpc.rx.binary.json-cbor', binary, json, cbor],
35+
['rpc.rx.binary.json-msgpack', binary, json, msgpack],
36+
['rpc.rx.binary.cbor-json', binary, cbor, json],
37+
['rpc.rx.binary.cbor-msgpack', binary, cbor, msgpack],
38+
['rpc.rx.binary.msgpack-json', binary, msgpack, json],
39+
['rpc.rx.binary.msgpack-cbor', binary, msgpack, cbor],
40+
41+
['rpc.json2.verbose.json', jsonRpc2, json, json],
42+
['rpc.json2.verbose.cbor', jsonRpc2, cbor, cbor],
43+
['rpc.json2.verbose.msgpack', jsonRpc2, msgpack, msgpack],
44+
['rpc.json2.verbose.json-cbor', jsonRpc2, json, cbor],
45+
['rpc.json2.verbose.json-msgpack', jsonRpc2, json, msgpack],
46+
['rpc.json2.verbose.cbor-json', jsonRpc2, cbor, json],
47+
['rpc.json2.verbose.cbor-msgpack', jsonRpc2, cbor, msgpack],
48+
['rpc.json2.verbose.msgpack-json', jsonRpc2, msgpack, json],
49+
['rpc.json2.verbose.msgpack-cbor', jsonRpc2, msgpack, cbor],
50+
];
51+
52+
for (const [protocolSpecifier, msgCodec, reqCodec, resCodec] of cases) {
53+
const setup: ApiTestSetup = async () => {
54+
const port = +(process.env.PORT || 9999);
55+
const url = `ws://localhost:${port}/rpc`;
56+
const codec = new RpcCodec(msgCodec, reqCodec, resCodec);
57+
const client = new RpcPersistentClient({
58+
codec,
59+
channel: {
60+
newChannel: () =>
61+
new WebSocketChannel({
62+
newSocket: () => new WebSocket(url, [codec.specifier()]) as any,
63+
}),
64+
},
65+
});
66+
client.start();
67+
return {client};
68+
};
69+
describe(`protocol: application/x.${protocolSpecifier}`, () => {
70+
runApiTests(setup);
71+
});
72+
}
73+
} else {
74+
test.skip('set TEST_E2E=1 env var to run this test suite', () => {});
75+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {CborJsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/cbor';
2+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
3+
import {RpcPersistentClient, WebSocketChannel} from '../common';
4+
import {RpcCodec} from '../common/codec/RpcCodec';
5+
import {BinaryRpcMessageCodec} from '../common/codec/binary';
6+
7+
/**
8+
* Constructs a JSON Reactive RPC client.
9+
* @param url RPC endpoint.
10+
* @param token Authentication token.
11+
* @returns An RPC client.
12+
*/
13+
export const createBinaryWsRpcClient = (url: string, token: string) => {
14+
const writer = new Writer(1024 * 4);
15+
const msg = new BinaryRpcMessageCodec();
16+
const req = new CborJsonValueCodec(writer);
17+
const codec = new RpcCodec(msg, req, req);
18+
const client = new RpcPersistentClient({
19+
codec,
20+
channel: {
21+
newChannel: () =>
22+
new WebSocketChannel({
23+
newSocket: () => new WebSocket(url, [codec.specifier(), token]),
24+
}),
25+
},
26+
});
27+
client.start();
28+
return client;
29+
};

0 commit comments

Comments
 (0)