@replit/river 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/bandwidth.bench.d.ts +1 -0
- package/dist/__tests__/bandwidth.bench.d.ts.map +1 -0
- package/dist/__tests__/bandwidth.bench.js +12 -5
- package/dist/__tests__/e2e.test.d.ts +2 -0
- package/dist/__tests__/e2e.test.d.ts.map +1 -0
- package/dist/__tests__/e2e.test.js +153 -0
- package/dist/__tests__/fixtures.d.ts +155 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +129 -0
- package/dist/__tests__/handler.test.d.ts +2 -0
- package/dist/__tests__/handler.test.d.ts.map +1 -0
- package/dist/__tests__/handler.test.js +71 -0
- package/dist/__tests__/serialize.test.d.ts +2 -0
- package/dist/__tests__/serialize.test.d.ts.map +1 -0
- package/dist/__tests__/serialize.test.js +135 -0
- package/dist/__tests__/typescript-stress.test.d.ts +736 -98
- package/dist/__tests__/typescript-stress.test.d.ts.map +1 -0
- package/dist/__tests__/typescript-stress.test.js +13 -1
- package/dist/codec/codec.test.d.ts +1 -0
- package/dist/codec/codec.test.d.ts.map +1 -0
- package/dist/codec/index.d.ts +1 -0
- package/dist/codec/index.d.ts.map +1 -0
- package/dist/codec/json.d.ts +5 -0
- package/dist/codec/json.d.ts.map +1 -0
- package/dist/codec/json.js +4 -0
- package/dist/codec/types.d.ts +15 -0
- package/dist/codec/types.d.ts.map +1 -0
- package/dist/logging/index.d.ts +13 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +12 -0
- package/dist/router/builder.d.ts +91 -7
- package/dist/router/builder.d.ts.map +1 -0
- package/dist/router/builder.js +32 -0
- package/dist/router/client.d.ts +28 -3
- package/dist/router/client.d.ts.map +1 -0
- package/dist/router/client.js +37 -6
- package/dist/router/context.d.ts +2 -0
- package/dist/router/context.d.ts.map +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/result.d.ts +25 -0
- package/dist/router/result.d.ts.map +1 -0
- package/dist/router/result.js +18 -0
- package/dist/router/server.d.ts +13 -0
- package/dist/router/server.d.ts.map +1 -0
- package/dist/router/server.js +85 -56
- package/dist/testUtils.d.ts +69 -2
- package/dist/testUtils.d.ts.map +1 -0
- package/dist/testUtils.js +91 -4
- package/dist/transport/impls/stdio.d.ts +25 -0
- package/dist/transport/impls/stdio.d.ts.map +1 -0
- package/dist/transport/impls/stdio.js +24 -0
- package/dist/transport/impls/stdio.test.d.ts +1 -0
- package/dist/transport/impls/stdio.test.d.ts.map +1 -0
- package/dist/transport/impls/stdio.test.js +2 -8
- package/dist/transport/impls/ws.d.ts +40 -1
- package/dist/transport/impls/ws.d.ts.map +1 -0
- package/dist/transport/impls/ws.js +39 -2
- package/dist/transport/impls/ws.test.d.ts +1 -0
- package/dist/transport/impls/ws.test.d.ts.map +1 -0
- package/dist/transport/impls/ws.test.js +8 -20
- package/dist/transport/index.d.ts +9 -2
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +7 -1
- package/dist/transport/message.d.ts +94 -36
- package/dist/transport/message.d.ts.map +1 -0
- package/dist/transport/message.js +66 -19
- package/dist/transport/message.test.d.ts +1 -0
- package/dist/transport/message.test.d.ts.map +1 -0
- package/dist/transport/message.test.js +39 -6
- package/dist/transport/types.d.ts +38 -2
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +44 -5
- package/package.json +1 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth.bench.d.ts","sourceRoot":"","sources":["../../__tests__/bandwidth.bench.ts"],"names":[],"mappings":""}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
|
-
import { bench, describe } from 'vitest';
|
|
2
|
+
import { assert, bench, describe } from 'vitest';
|
|
3
3
|
import { createWebSocketServer, createWsTransports, onServerReady, } from '../testUtils';
|
|
4
4
|
import largePayload from './largePayload.json';
|
|
5
|
-
import { TestServiceConstructor } from './
|
|
5
|
+
import { TestServiceConstructor } from './fixtures';
|
|
6
6
|
import { createServer } from '../router/server';
|
|
7
7
|
import { createClient } from '../router/client';
|
|
8
8
|
import { StupidlyLargeService } from './typescript-stress.test';
|
|
@@ -15,6 +15,8 @@ const dummyPayloadSmall = () => ({
|
|
|
15
15
|
to: 'SERVER',
|
|
16
16
|
serviceName: 'test',
|
|
17
17
|
procedureName: 'test',
|
|
18
|
+
streamId: 'test',
|
|
19
|
+
controlFlags: 0,
|
|
18
20
|
payload: {
|
|
19
21
|
msg: 'cool',
|
|
20
22
|
},
|
|
@@ -25,6 +27,8 @@ const dummyPayloadLarge = () => ({
|
|
|
25
27
|
to: 'SERVER',
|
|
26
28
|
serviceName: 'test',
|
|
27
29
|
procedureName: 'test',
|
|
30
|
+
streamId: 'test',
|
|
31
|
+
controlFlags: 0,
|
|
28
32
|
payload: largePayload,
|
|
29
33
|
});
|
|
30
34
|
const BENCH_DURATION = 1000;
|
|
@@ -53,12 +57,14 @@ describe('simple router level bandwidth', async () => {
|
|
|
53
57
|
const server = await createServer(serverTransport, serviceDefs);
|
|
54
58
|
const client = createClient(clientTransport);
|
|
55
59
|
bench('rpc (wait for response)', async () => {
|
|
56
|
-
await client.test.add({ n: 1 });
|
|
60
|
+
const result = await client.test.add({ n: 1 });
|
|
61
|
+
assert(result.ok);
|
|
57
62
|
}, { time: BENCH_DURATION });
|
|
58
63
|
const [input, output] = await client.test.echo();
|
|
59
64
|
bench('stream (wait for response)', async () => {
|
|
60
65
|
input.push({ msg: 'abc', ignore: false });
|
|
61
|
-
await output.next();
|
|
66
|
+
const result = await output.next();
|
|
67
|
+
assert(result.value && result.value.ok);
|
|
62
68
|
}, { time: BENCH_DURATION });
|
|
63
69
|
bench('stream', async () => {
|
|
64
70
|
input.push({ msg: 'abc', ignore: false });
|
|
@@ -78,6 +84,7 @@ describe('complex (50 procedures) router level bandwidth', async () => {
|
|
|
78
84
|
const server = await createServer(serverTransport, serviceDefs);
|
|
79
85
|
const client = createClient(clientTransport);
|
|
80
86
|
bench('rpc (wait for response)', async () => {
|
|
81
|
-
await client.b.f35({ a: 1 });
|
|
87
|
+
const result = await client.b.f35({ a: 1 });
|
|
88
|
+
assert(result.ok);
|
|
82
89
|
}, { time: BENCH_DURATION });
|
|
83
90
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2e.test.d.ts","sourceRoot":"","sources":["../../__tests__/e2e.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { afterAll, assert, describe, expect, test } from 'vitest';
|
|
2
|
+
import { createWebSocketServer, createWsTransports, onServerReady, } from '../testUtils';
|
|
3
|
+
import { createServer } from '../router/server';
|
|
4
|
+
import { createClient } from '../router/client';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
import { DIV_BY_ZERO, FallibleServiceConstructor, OrderingServiceConstructor, STREAM_ERROR, TestServiceConstructor, } from './fixtures';
|
|
7
|
+
import { UNCAUGHT_ERROR } from '../router/result';
|
|
8
|
+
describe('client <-> server integration test', async () => {
|
|
9
|
+
const server = http.createServer();
|
|
10
|
+
const port = await onServerReady(server);
|
|
11
|
+
const webSocketServer = await createWebSocketServer(server);
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
webSocketServer.clients.forEach((socket) => {
|
|
14
|
+
socket.close();
|
|
15
|
+
});
|
|
16
|
+
server.close();
|
|
17
|
+
});
|
|
18
|
+
test('rpc', async () => {
|
|
19
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
20
|
+
const serviceDefs = { test: TestServiceConstructor() };
|
|
21
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
22
|
+
const client = createClient(clientTransport);
|
|
23
|
+
const result = await client.test.add({ n: 3 });
|
|
24
|
+
assert(result.ok);
|
|
25
|
+
expect(result.payload).toStrictEqual({ result: 3 });
|
|
26
|
+
});
|
|
27
|
+
test('fallible rpc', async () => {
|
|
28
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
29
|
+
const serviceDefs = { test: FallibleServiceConstructor() };
|
|
30
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
31
|
+
const client = createClient(clientTransport);
|
|
32
|
+
const result = await client.test.divide({ a: 10, b: 2 });
|
|
33
|
+
assert(result.ok);
|
|
34
|
+
expect(result.payload).toStrictEqual({ result: 5 });
|
|
35
|
+
const result2 = await client.test.divide({ a: 10, b: 0 });
|
|
36
|
+
assert(!result2.ok);
|
|
37
|
+
expect(result2.payload).toStrictEqual({
|
|
38
|
+
code: DIV_BY_ZERO,
|
|
39
|
+
message: 'Cannot divide by zero',
|
|
40
|
+
extras: {
|
|
41
|
+
test: 'abc',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
test('stream', async () => {
|
|
46
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
47
|
+
const serviceDefs = { test: TestServiceConstructor() };
|
|
48
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
49
|
+
const client = createClient(clientTransport);
|
|
50
|
+
const [input, output, close] = await client.test.echo();
|
|
51
|
+
input.push({ msg: 'abc', ignore: false });
|
|
52
|
+
input.push({ msg: 'def', ignore: true });
|
|
53
|
+
input.push({ msg: 'ghi', ignore: false });
|
|
54
|
+
input.end();
|
|
55
|
+
const result1 = await output.next().then((res) => res.value);
|
|
56
|
+
assert(result1.ok);
|
|
57
|
+
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
58
|
+
const result2 = await output.next().then((res) => res.value);
|
|
59
|
+
assert(result2.ok);
|
|
60
|
+
expect(result2.payload).toStrictEqual({ response: 'ghi' });
|
|
61
|
+
close();
|
|
62
|
+
});
|
|
63
|
+
test('fallible stream', async () => {
|
|
64
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
65
|
+
const serviceDefs = { test: FallibleServiceConstructor() };
|
|
66
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
67
|
+
const client = createClient(clientTransport);
|
|
68
|
+
const [input, output, close] = await client.test.echo();
|
|
69
|
+
input.push({ msg: 'abc', throwResult: false, throwError: false });
|
|
70
|
+
const result1 = await output.next().then((res) => res.value);
|
|
71
|
+
assert(result1 && result1.ok);
|
|
72
|
+
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
73
|
+
input.push({ msg: 'def', throwResult: true, throwError: false });
|
|
74
|
+
const result2 = await output.next().then((res) => res.value);
|
|
75
|
+
assert(result2 && !result2.ok);
|
|
76
|
+
expect(result2.payload.code).toStrictEqual(STREAM_ERROR);
|
|
77
|
+
input.push({ msg: 'ghi', throwResult: false, throwError: true });
|
|
78
|
+
const result3 = await output.next().then((res) => res.value);
|
|
79
|
+
assert(result3 && !result3.ok);
|
|
80
|
+
expect(result3.payload).toStrictEqual({
|
|
81
|
+
code: UNCAUGHT_ERROR,
|
|
82
|
+
message: 'some message',
|
|
83
|
+
});
|
|
84
|
+
close();
|
|
85
|
+
});
|
|
86
|
+
test('message order is preserved in the face of disconnects', async () => {
|
|
87
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
88
|
+
const serviceDefs = { test: OrderingServiceConstructor() };
|
|
89
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
90
|
+
const client = createClient(clientTransport);
|
|
91
|
+
const expected = [];
|
|
92
|
+
for (let i = 0; i < 50; i++) {
|
|
93
|
+
expected.push(i);
|
|
94
|
+
if (i == 10) {
|
|
95
|
+
clientTransport.ws?.close();
|
|
96
|
+
}
|
|
97
|
+
if (i == 42) {
|
|
98
|
+
clientTransport.ws?.terminate();
|
|
99
|
+
}
|
|
100
|
+
await client.test.add({
|
|
101
|
+
n: i,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const res = await client.test.getAll({});
|
|
105
|
+
assert(res.ok);
|
|
106
|
+
return expect(res.payload.msgs).toStrictEqual(expected);
|
|
107
|
+
});
|
|
108
|
+
const CONCURRENCY = 10;
|
|
109
|
+
test('concurrent rpcs', async () => {
|
|
110
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
111
|
+
const serviceDefs = { test: OrderingServiceConstructor() };
|
|
112
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
113
|
+
const client = createClient(clientTransport);
|
|
114
|
+
const promises = [];
|
|
115
|
+
for (let i = 0; i < CONCURRENCY; i++) {
|
|
116
|
+
promises.push(client.test.add({ n: i }));
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < CONCURRENCY; i++) {
|
|
119
|
+
const result = await promises[i];
|
|
120
|
+
assert(result.ok);
|
|
121
|
+
expect(result.payload).toStrictEqual({ n: i });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
test('concurrent streams', async () => {
|
|
125
|
+
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
|
|
126
|
+
const serviceDefs = { test: TestServiceConstructor() };
|
|
127
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
128
|
+
const client = createClient(clientTransport);
|
|
129
|
+
const openStreams = [];
|
|
130
|
+
for (let i = 0; i < CONCURRENCY; i++) {
|
|
131
|
+
const streamHandle = await client.test.echo();
|
|
132
|
+
const input = streamHandle[0];
|
|
133
|
+
input.push({ msg: `${i}-1`, ignore: false });
|
|
134
|
+
input.push({ msg: `${i}-2`, ignore: false });
|
|
135
|
+
openStreams.push(streamHandle);
|
|
136
|
+
}
|
|
137
|
+
for (let i = 0; i < CONCURRENCY; i++) {
|
|
138
|
+
const output = openStreams[i][1];
|
|
139
|
+
const result1 = await output.next().then((res) => res.value);
|
|
140
|
+
assert(result1.ok);
|
|
141
|
+
expect(result1.payload).toStrictEqual({ response: `${i}-1` });
|
|
142
|
+
const result2 = await output.next().then((res) => res.value);
|
|
143
|
+
assert(result2.ok);
|
|
144
|
+
expect(result2.payload).toStrictEqual({ response: `${i}-2` });
|
|
145
|
+
}
|
|
146
|
+
// cleanup
|
|
147
|
+
for (let i = 0; i < CONCURRENCY; i++) {
|
|
148
|
+
const [input, _output, close] = openStreams[i];
|
|
149
|
+
input.end();
|
|
150
|
+
close();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export declare const EchoRequest: import("@sinclair/typebox").TObject<{
|
|
2
|
+
msg: import("@sinclair/typebox").TString;
|
|
3
|
+
ignore: import("@sinclair/typebox").TBoolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare const EchoResponse: import("@sinclair/typebox").TObject<{
|
|
6
|
+
response: import("@sinclair/typebox").TString;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const TestServiceConstructor: () => {
|
|
9
|
+
name: "test";
|
|
10
|
+
state: {
|
|
11
|
+
count: number;
|
|
12
|
+
};
|
|
13
|
+
procedures: {
|
|
14
|
+
add: {
|
|
15
|
+
input: import("@sinclair/typebox").TObject<{
|
|
16
|
+
n: import("@sinclair/typebox").TNumber;
|
|
17
|
+
}>;
|
|
18
|
+
output: import("@sinclair/typebox").TObject<{
|
|
19
|
+
result: import("@sinclair/typebox").TNumber;
|
|
20
|
+
}>;
|
|
21
|
+
errors: import("@sinclair/typebox").TNever;
|
|
22
|
+
handler: (context: import("../router").ServiceContextWithState<{
|
|
23
|
+
count: number;
|
|
24
|
+
}>, input: import("../transport/message").TransportMessage<{
|
|
25
|
+
n: number;
|
|
26
|
+
}>) => Promise<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
27
|
+
result: number;
|
|
28
|
+
}, never>>>;
|
|
29
|
+
type: "rpc";
|
|
30
|
+
};
|
|
31
|
+
} & {
|
|
32
|
+
echo: {
|
|
33
|
+
input: import("@sinclair/typebox").TObject<{
|
|
34
|
+
msg: import("@sinclair/typebox").TString;
|
|
35
|
+
ignore: import("@sinclair/typebox").TBoolean;
|
|
36
|
+
}>;
|
|
37
|
+
output: import("@sinclair/typebox").TObject<{
|
|
38
|
+
response: import("@sinclair/typebox").TString;
|
|
39
|
+
}>;
|
|
40
|
+
errors: import("@sinclair/typebox").TNever;
|
|
41
|
+
handler: (context: import("../router").ServiceContextWithState<{
|
|
42
|
+
count: number;
|
|
43
|
+
}>, input: AsyncIterable<import("../transport/message").TransportMessage<{
|
|
44
|
+
msg: string;
|
|
45
|
+
ignore: boolean;
|
|
46
|
+
}>>, output: import("it-pushable").Pushable<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
47
|
+
response: string;
|
|
48
|
+
}, never>>, void, unknown>) => Promise<void>;
|
|
49
|
+
type: "stream";
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export declare const OrderingServiceConstructor: () => {
|
|
54
|
+
name: "test";
|
|
55
|
+
state: {
|
|
56
|
+
msgs: number[];
|
|
57
|
+
};
|
|
58
|
+
procedures: {
|
|
59
|
+
add: {
|
|
60
|
+
input: import("@sinclair/typebox").TObject<{
|
|
61
|
+
n: import("@sinclair/typebox").TNumber;
|
|
62
|
+
}>;
|
|
63
|
+
output: import("@sinclair/typebox").TObject<{
|
|
64
|
+
n: import("@sinclair/typebox").TNumber;
|
|
65
|
+
}>;
|
|
66
|
+
errors: import("@sinclair/typebox").TNever;
|
|
67
|
+
handler: (context: import("../router").ServiceContextWithState<{
|
|
68
|
+
msgs: number[];
|
|
69
|
+
}>, input: import("../transport/message").TransportMessage<{
|
|
70
|
+
n: number;
|
|
71
|
+
}>) => Promise<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
72
|
+
n: number;
|
|
73
|
+
}, never>>>;
|
|
74
|
+
type: "rpc";
|
|
75
|
+
};
|
|
76
|
+
} & {
|
|
77
|
+
getAll: {
|
|
78
|
+
input: import("@sinclair/typebox").TObject<{}>;
|
|
79
|
+
output: import("@sinclair/typebox").TObject<{
|
|
80
|
+
msgs: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>;
|
|
81
|
+
}>;
|
|
82
|
+
errors: import("@sinclair/typebox").TNever;
|
|
83
|
+
handler: (context: import("../router").ServiceContextWithState<{
|
|
84
|
+
msgs: number[];
|
|
85
|
+
}>, input: import("../transport/message").TransportMessage<{}>) => Promise<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
86
|
+
msgs: number[];
|
|
87
|
+
}, never>>>;
|
|
88
|
+
type: "rpc";
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export declare const DIV_BY_ZERO = "DIV_BY_ZERO";
|
|
93
|
+
export declare const STREAM_ERROR = "STREAM_ERROR";
|
|
94
|
+
export declare const FallibleServiceConstructor: () => {
|
|
95
|
+
name: "fallible";
|
|
96
|
+
state: {};
|
|
97
|
+
procedures: {
|
|
98
|
+
divide: {
|
|
99
|
+
input: import("@sinclair/typebox").TObject<{
|
|
100
|
+
a: import("@sinclair/typebox").TNumber;
|
|
101
|
+
b: import("@sinclair/typebox").TNumber;
|
|
102
|
+
}>;
|
|
103
|
+
output: import("@sinclair/typebox").TObject<{
|
|
104
|
+
result: import("@sinclair/typebox").TNumber;
|
|
105
|
+
}>;
|
|
106
|
+
errors: import("@sinclair/typebox").TObject<{
|
|
107
|
+
code: import("@sinclair/typebox").TLiteral<"DIV_BY_ZERO">;
|
|
108
|
+
message: import("@sinclair/typebox").TString;
|
|
109
|
+
extras: import("@sinclair/typebox").TObject<{
|
|
110
|
+
test: import("@sinclair/typebox").TString;
|
|
111
|
+
}>;
|
|
112
|
+
}>;
|
|
113
|
+
handler: (context: import("../router").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
|
|
114
|
+
a: number;
|
|
115
|
+
b: number;
|
|
116
|
+
}>) => Promise<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
117
|
+
result: number;
|
|
118
|
+
}, {
|
|
119
|
+
message: string;
|
|
120
|
+
code: "DIV_BY_ZERO";
|
|
121
|
+
extras: {
|
|
122
|
+
test: string;
|
|
123
|
+
};
|
|
124
|
+
}>>>;
|
|
125
|
+
type: "rpc";
|
|
126
|
+
};
|
|
127
|
+
} & {
|
|
128
|
+
echo: {
|
|
129
|
+
input: import("@sinclair/typebox").TObject<{
|
|
130
|
+
msg: import("@sinclair/typebox").TString;
|
|
131
|
+
throwResult: import("@sinclair/typebox").TBoolean;
|
|
132
|
+
throwError: import("@sinclair/typebox").TBoolean;
|
|
133
|
+
}>;
|
|
134
|
+
output: import("@sinclair/typebox").TObject<{
|
|
135
|
+
response: import("@sinclair/typebox").TString;
|
|
136
|
+
}>;
|
|
137
|
+
errors: import("@sinclair/typebox").TObject<{
|
|
138
|
+
code: import("@sinclair/typebox").TLiteral<"STREAM_ERROR">;
|
|
139
|
+
message: import("@sinclair/typebox").TString;
|
|
140
|
+
}>;
|
|
141
|
+
handler: (context: import("../router").ServiceContextWithState<{}>, input: AsyncIterable<import("../transport/message").TransportMessage<{
|
|
142
|
+
msg: string;
|
|
143
|
+
throwResult: boolean;
|
|
144
|
+
throwError: boolean;
|
|
145
|
+
}>>, output: import("it-pushable").Pushable<import("../transport/message").TransportMessage<import("../router/result").Result<{
|
|
146
|
+
response: string;
|
|
147
|
+
}, {
|
|
148
|
+
message: string;
|
|
149
|
+
code: "STREAM_ERROR";
|
|
150
|
+
}>>, void, unknown>) => Promise<void>;
|
|
151
|
+
type: "stream";
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../__tests__/fixtures.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,WAAW;;;EAGtB,CAAC;AACH,eAAO,MAAM,YAAY;;EAA2C,CAAC;AAErE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BpB,CAAC;AAEhB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyBxB,CAAC;AAEhB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiExB,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { ServiceBuilder } from '../router/builder';
|
|
3
|
+
import { reply } from '../transport/message';
|
|
4
|
+
import { Err, Ok } from '../router/result';
|
|
5
|
+
export const EchoRequest = Type.Object({
|
|
6
|
+
msg: Type.String(),
|
|
7
|
+
ignore: Type.Boolean(),
|
|
8
|
+
});
|
|
9
|
+
export const EchoResponse = Type.Object({ response: Type.String() });
|
|
10
|
+
export const TestServiceConstructor = () => ServiceBuilder.create('test')
|
|
11
|
+
.initialState({
|
|
12
|
+
count: 0,
|
|
13
|
+
})
|
|
14
|
+
.defineProcedure('add', {
|
|
15
|
+
type: 'rpc',
|
|
16
|
+
input: Type.Object({ n: Type.Number() }),
|
|
17
|
+
output: Type.Object({ result: Type.Number() }),
|
|
18
|
+
errors: Type.Never(),
|
|
19
|
+
async handler(ctx, msg) {
|
|
20
|
+
const { n } = msg.payload;
|
|
21
|
+
ctx.state.count += n;
|
|
22
|
+
return reply(msg, Ok({ result: ctx.state.count }));
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.defineProcedure('echo', {
|
|
26
|
+
type: 'stream',
|
|
27
|
+
input: EchoRequest,
|
|
28
|
+
output: EchoResponse,
|
|
29
|
+
errors: Type.Never(),
|
|
30
|
+
async handler(_ctx, msgStream, returnStream) {
|
|
31
|
+
for await (const msg of msgStream) {
|
|
32
|
+
const req = msg.payload;
|
|
33
|
+
if (!req.ignore) {
|
|
34
|
+
returnStream.push(reply(msg, Ok({ response: req.msg })));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
.finalize();
|
|
40
|
+
export const OrderingServiceConstructor = () => ServiceBuilder.create('test')
|
|
41
|
+
.initialState({
|
|
42
|
+
msgs: [],
|
|
43
|
+
})
|
|
44
|
+
.defineProcedure('add', {
|
|
45
|
+
type: 'rpc',
|
|
46
|
+
input: Type.Object({ n: Type.Number() }),
|
|
47
|
+
output: Type.Object({ n: Type.Number() }),
|
|
48
|
+
errors: Type.Never(),
|
|
49
|
+
async handler(ctx, msg) {
|
|
50
|
+
const { n } = msg.payload;
|
|
51
|
+
ctx.state.msgs.push(n);
|
|
52
|
+
return reply(msg, Ok({ n }));
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
.defineProcedure('getAll', {
|
|
56
|
+
type: 'rpc',
|
|
57
|
+
input: Type.Object({}),
|
|
58
|
+
output: Type.Object({ msgs: Type.Array(Type.Number()) }),
|
|
59
|
+
errors: Type.Never(),
|
|
60
|
+
async handler(ctx, msg) {
|
|
61
|
+
return reply(msg, Ok({ msgs: ctx.state.msgs }));
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
.finalize();
|
|
65
|
+
export const DIV_BY_ZERO = 'DIV_BY_ZERO';
|
|
66
|
+
export const STREAM_ERROR = 'STREAM_ERROR';
|
|
67
|
+
export const FallibleServiceConstructor = () => ServiceBuilder.create('fallible')
|
|
68
|
+
.initialState({})
|
|
69
|
+
.defineProcedure('divide', {
|
|
70
|
+
type: 'rpc',
|
|
71
|
+
input: Type.Object({ a: Type.Number(), b: Type.Number() }),
|
|
72
|
+
output: Type.Object({ result: Type.Number() }),
|
|
73
|
+
errors: Type.Union([
|
|
74
|
+
Type.Object({
|
|
75
|
+
code: Type.Literal(DIV_BY_ZERO),
|
|
76
|
+
message: Type.String(),
|
|
77
|
+
extras: Type.Object({ test: Type.String() }),
|
|
78
|
+
}),
|
|
79
|
+
]),
|
|
80
|
+
async handler(_ctx, msg) {
|
|
81
|
+
const { a, b } = msg.payload;
|
|
82
|
+
if (b === 0) {
|
|
83
|
+
return reply(msg, {
|
|
84
|
+
ok: false,
|
|
85
|
+
payload: {
|
|
86
|
+
code: DIV_BY_ZERO,
|
|
87
|
+
message: 'Cannot divide by zero',
|
|
88
|
+
extras: { test: 'abc' },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return reply(msg, Ok({ result: a / b }));
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
.defineProcedure('echo', {
|
|
98
|
+
type: 'stream',
|
|
99
|
+
input: Type.Object({
|
|
100
|
+
msg: Type.String(),
|
|
101
|
+
throwResult: Type.Boolean(),
|
|
102
|
+
throwError: Type.Boolean(),
|
|
103
|
+
}),
|
|
104
|
+
output: Type.Object({ response: Type.String() }),
|
|
105
|
+
errors: Type.Union([
|
|
106
|
+
Type.Object({
|
|
107
|
+
code: Type.Literal(STREAM_ERROR),
|
|
108
|
+
message: Type.String(),
|
|
109
|
+
}),
|
|
110
|
+
]),
|
|
111
|
+
async handler(_ctx, msgStream, returnStream) {
|
|
112
|
+
for await (const msg of msgStream) {
|
|
113
|
+
const req = msg.payload;
|
|
114
|
+
if (req.throwError) {
|
|
115
|
+
throw new Error('some message');
|
|
116
|
+
}
|
|
117
|
+
else if (req.throwResult) {
|
|
118
|
+
returnStream.push(reply(msg, Err({
|
|
119
|
+
code: STREAM_ERROR,
|
|
120
|
+
message: 'field throwResult was set to true',
|
|
121
|
+
})));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
returnStream.push(reply(msg, Ok({ response: req.msg })));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
.finalize();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.test.d.ts","sourceRoot":"","sources":["../../__tests__/handler.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { asClientRpc, asClientStream } from '../testUtils';
|
|
2
|
+
import { assert, describe, expect, test } from 'vitest';
|
|
3
|
+
import { DIV_BY_ZERO, FallibleServiceConstructor, STREAM_ERROR, TestServiceConstructor, } from './fixtures';
|
|
4
|
+
import { UNCAUGHT_ERROR } from '../router/result';
|
|
5
|
+
describe('server-side test', () => {
|
|
6
|
+
const service = TestServiceConstructor();
|
|
7
|
+
const initialState = { count: 0 };
|
|
8
|
+
test('rpc basic', async () => {
|
|
9
|
+
const add = asClientRpc(initialState, service.procedures.add);
|
|
10
|
+
const result = await add({ n: 3 });
|
|
11
|
+
assert(result.ok);
|
|
12
|
+
expect(result.payload).toStrictEqual({ result: 3 });
|
|
13
|
+
});
|
|
14
|
+
test('rpc initial state', async () => {
|
|
15
|
+
const add = asClientRpc({ count: 5 }, service.procedures.add);
|
|
16
|
+
const result = await add({ n: 6 });
|
|
17
|
+
assert(result.ok);
|
|
18
|
+
expect(result.payload).toStrictEqual({ result: 11 });
|
|
19
|
+
});
|
|
20
|
+
test('fallible rpc', async () => {
|
|
21
|
+
const service = FallibleServiceConstructor();
|
|
22
|
+
const divide = asClientRpc({}, service.procedures.divide);
|
|
23
|
+
const result = await divide({ a: 10, b: 2 });
|
|
24
|
+
assert(result.ok);
|
|
25
|
+
expect(result.payload).toStrictEqual({ result: 5 });
|
|
26
|
+
const result2 = await divide({ a: 10, b: 0 });
|
|
27
|
+
assert(!result2.ok);
|
|
28
|
+
expect(result2.payload).toStrictEqual({
|
|
29
|
+
code: DIV_BY_ZERO,
|
|
30
|
+
message: 'Cannot divide by zero',
|
|
31
|
+
extras: {
|
|
32
|
+
test: 'abc',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
test('stream basic', async () => {
|
|
37
|
+
const [input, output] = asClientStream(initialState, service.procedures.echo);
|
|
38
|
+
input.push({ msg: 'abc', ignore: false });
|
|
39
|
+
input.push({ msg: 'def', ignore: true });
|
|
40
|
+
input.push({ msg: 'ghi', ignore: false });
|
|
41
|
+
input.end();
|
|
42
|
+
const result1 = await output.next().then((res) => res.value);
|
|
43
|
+
assert(result1 && result1.ok);
|
|
44
|
+
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
45
|
+
const result2 = await output.next().then((res) => res.value);
|
|
46
|
+
assert(result2 && result2.ok);
|
|
47
|
+
expect(result2.payload).toStrictEqual({ response: 'ghi' });
|
|
48
|
+
expect(output.readableLength).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
test('fallible stream', async () => {
|
|
51
|
+
const service = FallibleServiceConstructor();
|
|
52
|
+
const [input, output] = asClientStream({}, service.procedures.echo);
|
|
53
|
+
input.push({ msg: 'abc', throwResult: false, throwError: false });
|
|
54
|
+
const result1 = await output.next().then((res) => res.value);
|
|
55
|
+
assert(result1 && result1.ok);
|
|
56
|
+
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
57
|
+
input.push({ msg: 'def', throwResult: true, throwError: false });
|
|
58
|
+
const result2 = await output.next().then((res) => res.value);
|
|
59
|
+
assert(result2 && !result2.ok);
|
|
60
|
+
expect(result2.payload.code).toStrictEqual(STREAM_ERROR);
|
|
61
|
+
input.push({ msg: 'ghi', throwResult: false, throwError: true });
|
|
62
|
+
const result3 = await output.next().then((res) => res.value);
|
|
63
|
+
assert(result3 && !result3.ok);
|
|
64
|
+
expect(result3.payload).toStrictEqual({
|
|
65
|
+
code: UNCAUGHT_ERROR,
|
|
66
|
+
message: 'some message',
|
|
67
|
+
});
|
|
68
|
+
input.end();
|
|
69
|
+
expect(output.readableLength).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.test.d.ts","sourceRoot":"","sources":["../../__tests__/serialize.test.ts"],"names":[],"mappings":""}
|