@replit/river 0.7.2 → 0.8.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.js +5 -5
- package/dist/__tests__/e2e.test.js +57 -19
- package/dist/__tests__/fixtures/observable.d.ts +26 -0
- package/dist/__tests__/fixtures/observable.d.ts.map +1 -0
- package/dist/__tests__/fixtures/observable.js +38 -0
- package/dist/__tests__/fixtures/observable.test.d.ts +2 -0
- package/dist/__tests__/fixtures/observable.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/observable.test.js +39 -0
- package/dist/__tests__/{fixtures.d.ts → fixtures/services.d.ts} +58 -18
- package/dist/__tests__/fixtures/services.d.ts.map +1 -0
- package/dist/__tests__/{fixtures.js → fixtures/services.js} +31 -3
- package/dist/__tests__/handler.test.js +24 -7
- package/dist/__tests__/serialize.test.js +1 -1
- package/dist/router/builder.d.ts +10 -4
- package/dist/router/builder.d.ts.map +1 -1
- package/dist/router/client.d.ts +14 -5
- package/dist/router/client.d.ts.map +1 -1
- package/dist/router/client.js +27 -5
- package/dist/router/server.d.ts.map +1 -1
- package/dist/router/server.js +28 -11
- package/dist/transport/impls/stdio/stdio.test.js +1 -1
- package/dist/transport/impls/ws/ws.test.js +1 -1
- package/dist/{testUtils.d.ts → util/testHelpers.d.ts} +27 -8
- package/dist/util/testHelpers.d.ts.map +1 -0
- package/dist/{testUtils.js → util/testHelpers.js} +51 -5
- package/package.json +2 -2
- package/dist/__tests__/fixtures.d.ts.map +0 -1
- package/dist/testUtils.d.ts.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { assert, bench, describe } from 'vitest';
|
|
3
|
-
import { createWebSocketServer, createWsTransports, onServerReady, } from '../
|
|
3
|
+
import { createWebSocketServer, createWsTransports, onServerReady, } from '../util/testHelpers';
|
|
4
4
|
import largePayload from './largePayload.json';
|
|
5
|
-
import { TestServiceConstructor } from './fixtures';
|
|
5
|
+
import { TestServiceConstructor } from './fixtures/services';
|
|
6
6
|
import { createServer } from '../router/server';
|
|
7
7
|
import { createClient } from '../router/client';
|
|
8
8
|
import { StupidlyLargeService } from './typescript-stress.test';
|
|
@@ -57,10 +57,10 @@ describe('simple router level bandwidth', async () => {
|
|
|
57
57
|
const server = await createServer(serverTransport, serviceDefs);
|
|
58
58
|
const client = createClient(clientTransport);
|
|
59
59
|
bench('rpc (wait for response)', async () => {
|
|
60
|
-
const result = await client.test.add({ n: 1 });
|
|
60
|
+
const result = await client.test.add.rpc({ n: 1 });
|
|
61
61
|
assert(result.ok);
|
|
62
62
|
}, { time: BENCH_DURATION });
|
|
63
|
-
const [input, output] = await client.test.echo();
|
|
63
|
+
const [input, output] = await client.test.echo.stream();
|
|
64
64
|
bench('stream (wait for response)', async () => {
|
|
65
65
|
input.push({ msg: 'abc', ignore: false });
|
|
66
66
|
const result = await output.next();
|
|
@@ -84,7 +84,7 @@ describe('complex (50 procedures) router level bandwidth', async () => {
|
|
|
84
84
|
const server = await createServer(serverTransport, serviceDefs);
|
|
85
85
|
const client = createClient(clientTransport);
|
|
86
86
|
bench('rpc (wait for response)', async () => {
|
|
87
|
-
const result = await client.b.f35({ a: 1 });
|
|
87
|
+
const result = await client.b.f35.rpc({ a: 1 });
|
|
88
88
|
assert(result.ok);
|
|
89
89
|
}, { time: BENCH_DURATION });
|
|
90
90
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { afterAll, assert, describe, expect, test } from 'vitest';
|
|
2
|
-
import { createWebSocketServer, createWsTransports, onServerReady, } from '../
|
|
2
|
+
import { createLocalWebSocketClient, createWebSocketServer, createWsTransports, iterNext, onServerReady, } from '../util/testHelpers';
|
|
3
3
|
import { createServer } from '../router/server';
|
|
4
4
|
import { createClient } from '../router/client';
|
|
5
5
|
import http from 'http';
|
|
6
|
-
import { BinaryFileServiceConstructor, DIV_BY_ZERO, FallibleServiceConstructor, OrderingServiceConstructor, STREAM_ERROR, TestServiceConstructor, } from './fixtures';
|
|
6
|
+
import { BinaryFileServiceConstructor, DIV_BY_ZERO, FallibleServiceConstructor, OrderingServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
|
|
7
7
|
import { UNCAUGHT_ERROR } from '../router/result';
|
|
8
8
|
import { codecs } from '../codec/codec.test';
|
|
9
|
+
import { WebSocketClientTransport } from '../transport/impls/ws/client';
|
|
10
|
+
import { WebSocketServerTransport } from '../transport/impls/ws/server';
|
|
9
11
|
describe.each(codecs)('client <-> server integration test ($name codec)', async ({ codec }) => {
|
|
10
12
|
const httpServer = http.createServer();
|
|
11
13
|
const port = await onServerReady(httpServer);
|
|
@@ -22,7 +24,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
22
24
|
const serviceDefs = { test: TestServiceConstructor() };
|
|
23
25
|
const server = await createServer(serverTransport, serviceDefs);
|
|
24
26
|
const client = createClient(clientTransport);
|
|
25
|
-
const result = await client.test.add({ n: 3 });
|
|
27
|
+
const result = await client.test.add.rpc({ n: 3 });
|
|
26
28
|
assert(result.ok);
|
|
27
29
|
expect(result.payload).toStrictEqual({ result: 3 });
|
|
28
30
|
});
|
|
@@ -31,10 +33,10 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
31
33
|
const serviceDefs = { test: FallibleServiceConstructor() };
|
|
32
34
|
const server = await createServer(serverTransport, serviceDefs);
|
|
33
35
|
const client = createClient(clientTransport);
|
|
34
|
-
const result = await client.test.divide({ a: 10, b: 2 });
|
|
36
|
+
const result = await client.test.divide.rpc({ a: 10, b: 2 });
|
|
35
37
|
assert(result.ok);
|
|
36
38
|
expect(result.payload).toStrictEqual({ result: 5 });
|
|
37
|
-
const result2 = await client.test.divide({ a: 10, b: 0 });
|
|
39
|
+
const result2 = await client.test.divide.rpc({ a: 10, b: 0 });
|
|
38
40
|
assert(!result2.ok);
|
|
39
41
|
expect(result2.payload).toStrictEqual({
|
|
40
42
|
code: DIV_BY_ZERO,
|
|
@@ -49,7 +51,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
49
51
|
const serviceDefs = { test: BinaryFileServiceConstructor() };
|
|
50
52
|
const server = await createServer(serverTransport, serviceDefs);
|
|
51
53
|
const client = createClient(clientTransport);
|
|
52
|
-
const result = await client.test.getFile({ file: 'test.py' });
|
|
54
|
+
const result = await client.test.getFile.rpc({ file: 'test.py' });
|
|
53
55
|
assert(result.ok);
|
|
54
56
|
assert(result.payload.contents instanceof Uint8Array);
|
|
55
57
|
expect(new TextDecoder().decode(result.payload.contents)).toStrictEqual('contents for file test.py');
|
|
@@ -59,15 +61,15 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
59
61
|
const serviceDefs = { test: TestServiceConstructor() };
|
|
60
62
|
const server = await createServer(serverTransport, serviceDefs);
|
|
61
63
|
const client = createClient(clientTransport);
|
|
62
|
-
const [input, output, close] = await client.test.echo();
|
|
64
|
+
const [input, output, close] = await client.test.echo.stream();
|
|
63
65
|
input.push({ msg: 'abc', ignore: false });
|
|
64
66
|
input.push({ msg: 'def', ignore: true });
|
|
65
67
|
input.push({ msg: 'ghi', ignore: false });
|
|
66
68
|
input.end();
|
|
67
|
-
const result1 = await output
|
|
69
|
+
const result1 = await iterNext(output);
|
|
68
70
|
assert(result1.ok);
|
|
69
71
|
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
70
|
-
const result2 = await output
|
|
72
|
+
const result2 = await iterNext(output);
|
|
71
73
|
assert(result2.ok);
|
|
72
74
|
expect(result2.payload).toStrictEqual({ response: 'ghi' });
|
|
73
75
|
close();
|
|
@@ -77,17 +79,17 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
77
79
|
const serviceDefs = { test: FallibleServiceConstructor() };
|
|
78
80
|
const server = await createServer(serverTransport, serviceDefs);
|
|
79
81
|
const client = createClient(clientTransport);
|
|
80
|
-
const [input, output, close] = await client.test.echo();
|
|
82
|
+
const [input, output, close] = await client.test.echo.stream();
|
|
81
83
|
input.push({ msg: 'abc', throwResult: false, throwError: false });
|
|
82
|
-
const result1 = await output
|
|
84
|
+
const result1 = await iterNext(output);
|
|
83
85
|
assert(result1 && result1.ok);
|
|
84
86
|
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
85
87
|
input.push({ msg: 'def', throwResult: true, throwError: false });
|
|
86
|
-
const result2 = await output
|
|
88
|
+
const result2 = await iterNext(output);
|
|
87
89
|
assert(result2 && !result2.ok);
|
|
88
90
|
expect(result2.payload.code).toStrictEqual(STREAM_ERROR);
|
|
89
91
|
input.push({ msg: 'ghi', throwResult: false, throwError: true });
|
|
90
|
-
const result3 = await output
|
|
92
|
+
const result3 = await iterNext(output);
|
|
91
93
|
assert(result3 && !result3.ok);
|
|
92
94
|
expect(result3.payload).toStrictEqual({
|
|
93
95
|
code: UNCAUGHT_ERROR,
|
|
@@ -95,6 +97,42 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
95
97
|
});
|
|
96
98
|
close();
|
|
97
99
|
});
|
|
100
|
+
test('subscription', async () => {
|
|
101
|
+
const options = { codec };
|
|
102
|
+
const serverTransport = new WebSocketServerTransport(webSocketServer, 'SERVER', options);
|
|
103
|
+
const client1Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client1', 'SERVER', options);
|
|
104
|
+
const client2Transport = new WebSocketClientTransport(() => createLocalWebSocketClient(port), 'client2', 'SERVER', options);
|
|
105
|
+
const serviceDefs = { test: SubscribableServiceConstructor() };
|
|
106
|
+
const server = await createServer(serverTransport, serviceDefs);
|
|
107
|
+
const client1 = createClient(client1Transport);
|
|
108
|
+
const client2 = createClient(client2Transport);
|
|
109
|
+
const [subscription1, close1] = await client1.test.value.subscribe({});
|
|
110
|
+
let result = await iterNext(subscription1);
|
|
111
|
+
assert(result.ok);
|
|
112
|
+
expect(result.payload).toStrictEqual({ result: 0 });
|
|
113
|
+
const [subscription2, close2] = await client2.test.value.subscribe({});
|
|
114
|
+
result = await iterNext(subscription2);
|
|
115
|
+
assert(result.ok);
|
|
116
|
+
expect(result.payload).toStrictEqual({ result: 0 });
|
|
117
|
+
const add1 = await client1.test.add.rpc({ n: 1 });
|
|
118
|
+
assert(add1.ok);
|
|
119
|
+
result = await iterNext(subscription1);
|
|
120
|
+
assert(result.ok);
|
|
121
|
+
expect(result.payload).toStrictEqual({ result: 1 });
|
|
122
|
+
result = await iterNext(subscription2);
|
|
123
|
+
assert(result.ok);
|
|
124
|
+
expect(result.payload).toStrictEqual({ result: 1 });
|
|
125
|
+
const add2 = await client2.test.add.rpc({ n: 3 });
|
|
126
|
+
assert(add2.ok);
|
|
127
|
+
result = await iterNext(subscription1);
|
|
128
|
+
assert(result.ok);
|
|
129
|
+
expect(result.payload).toStrictEqual({ result: 4 });
|
|
130
|
+
result = await iterNext(subscription2);
|
|
131
|
+
assert(result.ok);
|
|
132
|
+
expect(result.payload).toStrictEqual({ result: 4 });
|
|
133
|
+
close1();
|
|
134
|
+
close2();
|
|
135
|
+
});
|
|
98
136
|
test('message order is preserved in the face of disconnects', async () => {
|
|
99
137
|
const [clientTransport, serverTransport] = getTransports();
|
|
100
138
|
const serviceDefs = { test: OrderingServiceConstructor() };
|
|
@@ -109,11 +147,11 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
109
147
|
if (i == 42) {
|
|
110
148
|
clientTransport.connections.forEach((conn) => conn.ws.terminate());
|
|
111
149
|
}
|
|
112
|
-
await client.test.add({
|
|
150
|
+
await client.test.add.rpc({
|
|
113
151
|
n: i,
|
|
114
152
|
});
|
|
115
153
|
}
|
|
116
|
-
const res = await client.test.getAll({});
|
|
154
|
+
const res = await client.test.getAll.rpc({});
|
|
117
155
|
assert(res.ok);
|
|
118
156
|
return expect(res.payload.msgs).toStrictEqual(expected);
|
|
119
157
|
});
|
|
@@ -125,7 +163,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
125
163
|
const client = createClient(clientTransport);
|
|
126
164
|
const promises = [];
|
|
127
165
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
128
|
-
promises.push(client.test.add({ n: i }));
|
|
166
|
+
promises.push(client.test.add.rpc({ n: i }));
|
|
129
167
|
}
|
|
130
168
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
131
169
|
const result = await promises[i];
|
|
@@ -140,7 +178,7 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
140
178
|
const client = createClient(clientTransport);
|
|
141
179
|
const openStreams = [];
|
|
142
180
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
143
|
-
const streamHandle = await client.test.echo();
|
|
181
|
+
const streamHandle = await client.test.echo.stream();
|
|
144
182
|
const input = streamHandle[0];
|
|
145
183
|
input.push({ msg: `${i}-1`, ignore: false });
|
|
146
184
|
input.push({ msg: `${i}-2`, ignore: false });
|
|
@@ -148,10 +186,10 @@ describe.each(codecs)('client <-> server integration test ($name codec)', async
|
|
|
148
186
|
}
|
|
149
187
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
150
188
|
const output = openStreams[i][1];
|
|
151
|
-
const result1 = await output
|
|
189
|
+
const result1 = await iterNext(output);
|
|
152
190
|
assert(result1.ok);
|
|
153
191
|
expect(result1.payload).toStrictEqual({ response: `${i}-1` });
|
|
154
|
-
const result2 = await output
|
|
192
|
+
const result2 = await iterNext(output);
|
|
155
193
|
assert(result2.ok);
|
|
156
194
|
expect(result2.payload).toStrictEqual({ response: `${i}-2` });
|
|
157
195
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents an observable value that can be subscribed to for changes.
|
|
3
|
+
* This should only be used in tests
|
|
4
|
+
* @template T - The type of the value being observed.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Observable<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
private listeners;
|
|
9
|
+
constructor(initialValue: T);
|
|
10
|
+
/**
|
|
11
|
+
* Gets the current value of the observable.
|
|
12
|
+
*/
|
|
13
|
+
get(): T;
|
|
14
|
+
/**
|
|
15
|
+
* Sets the current value of the observable. All listeners will get an update with this value.
|
|
16
|
+
* @param newValue - The new value to set.
|
|
17
|
+
*/
|
|
18
|
+
set(tx: (preValue: T) => T): void;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribes to changes in the observable value.
|
|
21
|
+
* @param listener - A callback function that will be called when the value changes.
|
|
22
|
+
* @returns A function that can be called to unsubscribe from further notifications.
|
|
23
|
+
*/
|
|
24
|
+
observe(listener: (val: T) => void): () => boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=observable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observable.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/observable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,CAAC,SAAS,CAAwB;gBAE7B,YAAY,EAAE,CAAC;IAK3B;;OAEG;IACH,GAAG;IAIH;;;OAGG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC;IAM1B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI;CAKnC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents an observable value that can be subscribed to for changes.
|
|
3
|
+
* This should only be used in tests
|
|
4
|
+
* @template T - The type of the value being observed.
|
|
5
|
+
*/
|
|
6
|
+
export class Observable {
|
|
7
|
+
value;
|
|
8
|
+
listeners;
|
|
9
|
+
constructor(initialValue) {
|
|
10
|
+
this.value = initialValue;
|
|
11
|
+
this.listeners = new Set();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Gets the current value of the observable.
|
|
15
|
+
*/
|
|
16
|
+
get() {
|
|
17
|
+
return this.value;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sets the current value of the observable. All listeners will get an update with this value.
|
|
21
|
+
* @param newValue - The new value to set.
|
|
22
|
+
*/
|
|
23
|
+
set(tx) {
|
|
24
|
+
const newValue = tx(this.value);
|
|
25
|
+
this.value = newValue;
|
|
26
|
+
this.listeners.forEach((listener) => listener(newValue));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Subscribes to changes in the observable value.
|
|
30
|
+
* @param listener - A callback function that will be called when the value changes.
|
|
31
|
+
* @returns A function that can be called to unsubscribe from further notifications.
|
|
32
|
+
*/
|
|
33
|
+
observe(listener) {
|
|
34
|
+
this.listeners.add(listener);
|
|
35
|
+
listener(this.get());
|
|
36
|
+
return () => this.listeners.delete(listener);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observable.test.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/observable.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Observable } from './observable';
|
|
2
|
+
import { describe, expect, test, vitest } from 'vitest';
|
|
3
|
+
describe('Observable', () => {
|
|
4
|
+
test('should set initial value correctly', () => {
|
|
5
|
+
const initialValue = 10;
|
|
6
|
+
const observable = new Observable(initialValue);
|
|
7
|
+
expect(observable.value).toBe(initialValue);
|
|
8
|
+
});
|
|
9
|
+
test('should update value correctly', () => {
|
|
10
|
+
const observable = new Observable(10);
|
|
11
|
+
const newValue = 20;
|
|
12
|
+
observable.set(() => newValue);
|
|
13
|
+
expect(observable.value).toBe(newValue);
|
|
14
|
+
});
|
|
15
|
+
test('should notify listeners when value changes', () => {
|
|
16
|
+
const observable = new Observable(10);
|
|
17
|
+
const listener = vitest.fn();
|
|
18
|
+
observable.observe(listener);
|
|
19
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
20
|
+
const newValue = 20;
|
|
21
|
+
observable.set(() => newValue);
|
|
22
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
23
|
+
expect(listener).toHaveBeenCalledWith(newValue);
|
|
24
|
+
});
|
|
25
|
+
test('should unsubscribe from notifications', () => {
|
|
26
|
+
const observable = new Observable(10);
|
|
27
|
+
const listener = vitest.fn();
|
|
28
|
+
const unsubscribe = observable.observe(listener);
|
|
29
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
30
|
+
const newValue = 20;
|
|
31
|
+
observable.set(() => newValue);
|
|
32
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
33
|
+
expect(listener).toHaveBeenCalledWith(newValue);
|
|
34
|
+
unsubscribe();
|
|
35
|
+
const anotherValue = 30;
|
|
36
|
+
observable.set(() => anotherValue);
|
|
37
|
+
expect(listener).toHaveBeenCalledTimes(2); // should not be called again after unsubscribing
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Observable } from './observable';
|
|
1
2
|
export declare const EchoRequest: import("@sinclair/typebox").TObject<{
|
|
2
3
|
msg: import("@sinclair/typebox").TString;
|
|
3
4
|
ignore: import("@sinclair/typebox").TBoolean;
|
|
@@ -19,11 +20,11 @@ export declare const TestServiceConstructor: () => {
|
|
|
19
20
|
result: import("@sinclair/typebox").TNumber;
|
|
20
21
|
}>;
|
|
21
22
|
errors: import("@sinclair/typebox").TNever;
|
|
22
|
-
handler: (context: import("
|
|
23
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
23
24
|
count: number;
|
|
24
|
-
}>, input: import("
|
|
25
|
+
}>, input: import("../../transport/message").TransportMessage<{
|
|
25
26
|
n: number;
|
|
26
|
-
}>) => Promise<import("
|
|
27
|
+
}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
27
28
|
result: number;
|
|
28
29
|
}, never>>>;
|
|
29
30
|
type: "rpc";
|
|
@@ -38,12 +39,12 @@ export declare const TestServiceConstructor: () => {
|
|
|
38
39
|
response: import("@sinclair/typebox").TString;
|
|
39
40
|
}>;
|
|
40
41
|
errors: import("@sinclair/typebox").TNever;
|
|
41
|
-
handler: (context: import("
|
|
42
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
42
43
|
count: number;
|
|
43
|
-
}>, input: AsyncIterable<import("
|
|
44
|
+
}>, input: AsyncIterable<import("../../transport/message").TransportMessage<{
|
|
44
45
|
msg: string;
|
|
45
46
|
ignore: boolean;
|
|
46
|
-
}>>, output: import("it-pushable").Pushable<import("
|
|
47
|
+
}>>, output: import("it-pushable").Pushable<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
47
48
|
response: string;
|
|
48
49
|
}, never>>, void, unknown>) => Promise<void>;
|
|
49
50
|
type: "stream";
|
|
@@ -64,11 +65,11 @@ export declare const OrderingServiceConstructor: () => {
|
|
|
64
65
|
n: import("@sinclair/typebox").TNumber;
|
|
65
66
|
}>;
|
|
66
67
|
errors: import("@sinclair/typebox").TNever;
|
|
67
|
-
handler: (context: import("
|
|
68
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
68
69
|
msgs: number[];
|
|
69
|
-
}>, input: import("
|
|
70
|
+
}>, input: import("../../transport/message").TransportMessage<{
|
|
70
71
|
n: number;
|
|
71
|
-
}>) => Promise<import("
|
|
72
|
+
}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
72
73
|
n: number;
|
|
73
74
|
}, never>>>;
|
|
74
75
|
type: "rpc";
|
|
@@ -80,9 +81,9 @@ export declare const OrderingServiceConstructor: () => {
|
|
|
80
81
|
msgs: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>;
|
|
81
82
|
}>;
|
|
82
83
|
errors: import("@sinclair/typebox").TNever;
|
|
83
|
-
handler: (context: import("
|
|
84
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
84
85
|
msgs: number[];
|
|
85
|
-
}>, input: import("
|
|
86
|
+
}>, input: import("../../transport/message").TransportMessage<{}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
86
87
|
msgs: number[];
|
|
87
88
|
}, never>>>;
|
|
88
89
|
type: "rpc";
|
|
@@ -101,9 +102,9 @@ export declare const BinaryFileServiceConstructor: () => {
|
|
|
101
102
|
contents: import("@sinclair/typebox").TUint8Array;
|
|
102
103
|
}>;
|
|
103
104
|
errors: import("@sinclair/typebox").TNever;
|
|
104
|
-
handler: (context: import("
|
|
105
|
+
handler: (context: import("../../router").ServiceContextWithState<{}>, input: import("../../transport/message").TransportMessage<{
|
|
105
106
|
file: string;
|
|
106
|
-
}>) => Promise<import("
|
|
107
|
+
}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
107
108
|
contents: Uint8Array;
|
|
108
109
|
}, never>>>;
|
|
109
110
|
type: "rpc";
|
|
@@ -131,10 +132,10 @@ export declare const FallibleServiceConstructor: () => {
|
|
|
131
132
|
test: import("@sinclair/typebox").TString;
|
|
132
133
|
}>;
|
|
133
134
|
}>;
|
|
134
|
-
handler: (context: import("
|
|
135
|
+
handler: (context: import("../../router").ServiceContextWithState<{}>, input: import("../../transport/message").TransportMessage<{
|
|
135
136
|
a: number;
|
|
136
137
|
b: number;
|
|
137
|
-
}>) => Promise<import("
|
|
138
|
+
}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
138
139
|
result: number;
|
|
139
140
|
}, {
|
|
140
141
|
message: string;
|
|
@@ -159,11 +160,11 @@ export declare const FallibleServiceConstructor: () => {
|
|
|
159
160
|
code: import("@sinclair/typebox").TLiteral<"STREAM_ERROR">;
|
|
160
161
|
message: import("@sinclair/typebox").TString;
|
|
161
162
|
}>;
|
|
162
|
-
handler: (context: import("
|
|
163
|
+
handler: (context: import("../../router").ServiceContextWithState<{}>, input: AsyncIterable<import("../../transport/message").TransportMessage<{
|
|
163
164
|
msg: string;
|
|
164
165
|
throwResult: boolean;
|
|
165
166
|
throwError: boolean;
|
|
166
|
-
}>>, output: import("it-pushable").Pushable<import("
|
|
167
|
+
}>>, output: import("it-pushable").Pushable<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
167
168
|
response: string;
|
|
168
169
|
}, {
|
|
169
170
|
message: string;
|
|
@@ -173,4 +174,43 @@ export declare const FallibleServiceConstructor: () => {
|
|
|
173
174
|
};
|
|
174
175
|
};
|
|
175
176
|
};
|
|
176
|
-
|
|
177
|
+
export declare const SubscribableServiceConstructor: () => {
|
|
178
|
+
name: "subscribable";
|
|
179
|
+
state: {
|
|
180
|
+
count: Observable<number>;
|
|
181
|
+
};
|
|
182
|
+
procedures: {
|
|
183
|
+
add: {
|
|
184
|
+
input: import("@sinclair/typebox").TObject<{
|
|
185
|
+
n: import("@sinclair/typebox").TNumber;
|
|
186
|
+
}>;
|
|
187
|
+
output: import("@sinclair/typebox").TObject<{
|
|
188
|
+
result: import("@sinclair/typebox").TNumber;
|
|
189
|
+
}>;
|
|
190
|
+
errors: import("@sinclair/typebox").TNever;
|
|
191
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
192
|
+
count: Observable<number>;
|
|
193
|
+
}>, input: import("../../transport/message").TransportMessage<{
|
|
194
|
+
n: number;
|
|
195
|
+
}>) => Promise<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
196
|
+
result: number;
|
|
197
|
+
}, never>>>;
|
|
198
|
+
type: "rpc";
|
|
199
|
+
};
|
|
200
|
+
} & {
|
|
201
|
+
value: {
|
|
202
|
+
input: import("@sinclair/typebox").TObject<{}>;
|
|
203
|
+
output: import("@sinclair/typebox").TObject<{
|
|
204
|
+
result: import("@sinclair/typebox").TNumber;
|
|
205
|
+
}>;
|
|
206
|
+
errors: import("@sinclair/typebox").TNever;
|
|
207
|
+
handler: (context: import("../../router").ServiceContextWithState<{
|
|
208
|
+
count: Observable<number>;
|
|
209
|
+
}>, input: import("../../transport/message").TransportMessage<{}>, output: import("it-pushable").Pushable<import("../../transport/message").TransportMessage<import("../../router/result").Result<{
|
|
210
|
+
result: number;
|
|
211
|
+
}, never>>, void, unknown>) => Promise<void>;
|
|
212
|
+
type: "subscription";
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../__tests__/fixtures/services.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,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,4BAA4B;;;;;;;;;;;;;;;;;;;;CAc1B,CAAC;AAEhB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiExB,CAAC;AAEhB,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B5B,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
|
-
import { ServiceBuilder } from '
|
|
3
|
-
import { reply } from '
|
|
4
|
-
import { Err, Ok } from '
|
|
2
|
+
import { ServiceBuilder } from '../../router/builder';
|
|
3
|
+
import { reply } from '../../transport/message';
|
|
4
|
+
import { Err, Ok } from '../../router/result';
|
|
5
|
+
import { Observable } from './observable';
|
|
5
6
|
export const EchoRequest = Type.Object({
|
|
6
7
|
msg: Type.String(),
|
|
7
8
|
ignore: Type.Boolean(),
|
|
@@ -139,3 +140,30 @@ export const FallibleServiceConstructor = () => ServiceBuilder.create('fallible'
|
|
|
139
140
|
},
|
|
140
141
|
})
|
|
141
142
|
.finalize();
|
|
143
|
+
export const SubscribableServiceConstructor = () => ServiceBuilder.create('subscribable')
|
|
144
|
+
.initialState({
|
|
145
|
+
count: new Observable(0),
|
|
146
|
+
})
|
|
147
|
+
.defineProcedure('add', {
|
|
148
|
+
type: 'rpc',
|
|
149
|
+
input: Type.Object({ n: Type.Number() }),
|
|
150
|
+
output: Type.Object({ result: Type.Number() }),
|
|
151
|
+
errors: Type.Never(),
|
|
152
|
+
async handler(ctx, msg) {
|
|
153
|
+
const { n } = msg.payload;
|
|
154
|
+
ctx.state.count.set((prev) => prev + n);
|
|
155
|
+
return reply(msg, Ok({ result: ctx.state.count.get() }));
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
.defineProcedure('value', {
|
|
159
|
+
type: 'subscription',
|
|
160
|
+
input: Type.Object({}),
|
|
161
|
+
output: Type.Object({ result: Type.Number() }),
|
|
162
|
+
errors: Type.Never(),
|
|
163
|
+
async handler(ctx, msg, returnStream) {
|
|
164
|
+
ctx.state.count.observe((count) => {
|
|
165
|
+
returnStream.push(reply(msg, Ok({ result: count })));
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
.finalize();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { asClientRpc, asClientStream } from '../
|
|
1
|
+
import { asClientRpc, asClientStream, asClientSubscription, iterNext, } from '../util/testHelpers';
|
|
2
2
|
import { assert, describe, expect, test } from 'vitest';
|
|
3
|
-
import { DIV_BY_ZERO, FallibleServiceConstructor, STREAM_ERROR, TestServiceConstructor, } from './fixtures';
|
|
3
|
+
import { DIV_BY_ZERO, FallibleServiceConstructor, STREAM_ERROR, SubscribableServiceConstructor, TestServiceConstructor, } from './fixtures/services';
|
|
4
4
|
import { UNCAUGHT_ERROR } from '../router/result';
|
|
5
|
+
import { Observable } from './fixtures/observable';
|
|
5
6
|
describe('server-side test', () => {
|
|
6
7
|
const service = TestServiceConstructor();
|
|
7
8
|
const initialState = { count: 0 };
|
|
@@ -39,10 +40,10 @@ describe('server-side test', () => {
|
|
|
39
40
|
input.push({ msg: 'def', ignore: true });
|
|
40
41
|
input.push({ msg: 'ghi', ignore: false });
|
|
41
42
|
input.end();
|
|
42
|
-
const result1 = await output
|
|
43
|
+
const result1 = await iterNext(output);
|
|
43
44
|
assert(result1 && result1.ok);
|
|
44
45
|
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
45
|
-
const result2 = await output
|
|
46
|
+
const result2 = await iterNext(output);
|
|
46
47
|
assert(result2 && result2.ok);
|
|
47
48
|
expect(result2.payload).toStrictEqual({ response: 'ghi' });
|
|
48
49
|
expect(output.readableLength).toBe(0);
|
|
@@ -51,15 +52,15 @@ describe('server-side test', () => {
|
|
|
51
52
|
const service = FallibleServiceConstructor();
|
|
52
53
|
const [input, output] = asClientStream({}, service.procedures.echo);
|
|
53
54
|
input.push({ msg: 'abc', throwResult: false, throwError: false });
|
|
54
|
-
const result1 = await output
|
|
55
|
+
const result1 = await iterNext(output);
|
|
55
56
|
assert(result1 && result1.ok);
|
|
56
57
|
expect(result1.payload).toStrictEqual({ response: 'abc' });
|
|
57
58
|
input.push({ msg: 'def', throwResult: true, throwError: false });
|
|
58
|
-
const result2 = await output
|
|
59
|
+
const result2 = await iterNext(output);
|
|
59
60
|
assert(result2 && !result2.ok);
|
|
60
61
|
expect(result2.payload.code).toStrictEqual(STREAM_ERROR);
|
|
61
62
|
input.push({ msg: 'ghi', throwResult: false, throwError: true });
|
|
62
|
-
const result3 = await output
|
|
63
|
+
const result3 = await iterNext(output);
|
|
63
64
|
assert(result3 && !result3.ok);
|
|
64
65
|
expect(result3.payload).toStrictEqual({
|
|
65
66
|
code: UNCAUGHT_ERROR,
|
|
@@ -68,4 +69,20 @@ describe('server-side test', () => {
|
|
|
68
69
|
input.end();
|
|
69
70
|
expect(output.readableLength).toBe(0);
|
|
70
71
|
});
|
|
72
|
+
test('subscriptions', async () => {
|
|
73
|
+
const service = SubscribableServiceConstructor();
|
|
74
|
+
const state = { count: new Observable(0) };
|
|
75
|
+
const add = asClientRpc(state, service.procedures.add);
|
|
76
|
+
const subscribe = asClientSubscription(state, service.procedures.value);
|
|
77
|
+
const stream = await subscribe({});
|
|
78
|
+
const streamResult1 = await iterNext(stream);
|
|
79
|
+
assert(streamResult1 && streamResult1.ok);
|
|
80
|
+
expect(streamResult1.payload).toStrictEqual({ result: 0 });
|
|
81
|
+
const result = await add({ n: 3 });
|
|
82
|
+
assert(result.ok);
|
|
83
|
+
expect(result.payload).toStrictEqual({ result: 3 });
|
|
84
|
+
const streamResult2 = await iterNext(stream);
|
|
85
|
+
assert(streamResult2 && streamResult1.ok);
|
|
86
|
+
expect(streamResult2.payload).toStrictEqual({ result: 3 });
|
|
87
|
+
});
|
|
71
88
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, describe, test } from 'vitest';
|
|
2
2
|
import { serializeService } from '../router/builder';
|
|
3
|
-
import { BinaryFileServiceConstructor, FallibleServiceConstructor, TestServiceConstructor, } from './fixtures';
|
|
3
|
+
import { BinaryFileServiceConstructor, FallibleServiceConstructor, TestServiceConstructor, } from './fixtures/services';
|
|
4
4
|
describe('serialize service to jsonschema', () => {
|
|
5
5
|
test('serialize basic service', () => {
|
|
6
6
|
const service = TestServiceConstructor();
|
package/dist/router/builder.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Result, RiverError, RiverUncaughtSchema } from './result';
|
|
|
6
6
|
/**
|
|
7
7
|
* The valid {@link Procedure} types.
|
|
8
8
|
*/
|
|
9
|
-
export type ValidProcType = 'stream' | '
|
|
9
|
+
export type ValidProcType = 'rpc' | 'stream' | 'subscription';
|
|
10
10
|
/**
|
|
11
11
|
* A generic procedure listing where the keys are the names of the procedures
|
|
12
12
|
* and the values are the {@link Procedure} definitions. This is not meant to
|
|
@@ -65,7 +65,7 @@ export type ProcType<S extends AnyService, ProcName extends keyof S['procedures'
|
|
|
65
65
|
/**
|
|
66
66
|
* Defines a Procedure type that can be either an RPC or a stream procedure.
|
|
67
67
|
* @template State - The TypeBox schema of the state object.
|
|
68
|
-
* @template Ty - The type of the procedure
|
|
68
|
+
* @template Ty - The type of the procedure.
|
|
69
69
|
* @template I - The TypeBox schema of the input object.
|
|
70
70
|
* @template O - The TypeBox schema of the output object.
|
|
71
71
|
*/
|
|
@@ -75,13 +75,19 @@ export type Procedure<State extends object | unknown, Ty extends ValidProcType,
|
|
|
75
75
|
errors: E;
|
|
76
76
|
handler: (context: ServiceContextWithState<State>, input: TransportMessage<Static<I>>) => Promise<TransportMessage<Result<Static<O>, Static<E>>>>;
|
|
77
77
|
type: Ty;
|
|
78
|
-
} : {
|
|
78
|
+
} : Ty extends 'stream' ? {
|
|
79
79
|
input: I;
|
|
80
80
|
output: O;
|
|
81
81
|
errors: E;
|
|
82
82
|
handler: (context: ServiceContextWithState<State>, input: AsyncIterable<TransportMessage<Static<I>>>, output: Pushable<TransportMessage<Result<Static<O>, Static<E>>>>) => Promise<void>;
|
|
83
83
|
type: Ty;
|
|
84
|
-
}
|
|
84
|
+
} : Ty extends 'subscription' ? {
|
|
85
|
+
input: I;
|
|
86
|
+
output: O;
|
|
87
|
+
errors: E;
|
|
88
|
+
handler: (context: ServiceContextWithState<State>, input: TransportMessage<Static<I>>, output: Pushable<TransportMessage<Result<Static<O>, Static<E>>>>) => Promise<void>;
|
|
89
|
+
type: Ty;
|
|
90
|
+
} : never;
|
|
85
91
|
export type AnyProcedure = Procedure<object, ValidProcType, TObject, TObject, RiverError>;
|
|
86
92
|
/**
|
|
87
93
|
* A builder class for creating River Services.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../router/builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAQ,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../router/builder.ts"],"names":[],"mappings":"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"}
|
package/dist/router/client.d.ts
CHANGED
|
@@ -12,11 +12,20 @@ type AsyncIter<T> = AsyncGenerator<T, T, unknown>;
|
|
|
12
12
|
* @template Router - The type of the Router.
|
|
13
13
|
*/
|
|
14
14
|
type ServiceClient<Router extends AnyService> = {
|
|
15
|
-
[ProcName in keyof Router['procedures']]: ProcType<Router, ProcName> extends 'rpc' ?
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
() =>
|
|
19
|
-
|
|
15
|
+
[ProcName in keyof Router['procedures']]: ProcType<Router, ProcName> extends 'rpc' ? {
|
|
16
|
+
rpc: (input: Static<ProcInput<Router, ProcName>>) => Promise<Result<Static<ProcOutput<Router, ProcName>>, Static<ProcErrors<Router, ProcName>>>>;
|
|
17
|
+
} : ProcType<Router, ProcName> extends 'stream' ? {
|
|
18
|
+
stream: () => Promise<[
|
|
19
|
+
Pushable<Static<ProcInput<Router, ProcName>>>,
|
|
20
|
+
AsyncIter<Result<Static<ProcOutput<Router, ProcName>>, Static<ProcErrors<Router, ProcName>>>>,
|
|
21
|
+
() => void
|
|
22
|
+
]>;
|
|
23
|
+
} : ProcType<Router, ProcName> extends 'subscription' ? {
|
|
24
|
+
subscribe: (input: Static<ProcInput<Router, ProcName>>) => Promise<[
|
|
25
|
+
AsyncIter<Result<Static<ProcOutput<Router, ProcName>>, Static<ProcErrors<Router, ProcName>>>>,
|
|
26
|
+
() => void
|
|
27
|
+
]>;
|
|
28
|
+
} : never;
|
|
20
29
|
};
|
|
21
30
|
/**
|
|
22
31
|
* Defines a type that represents a client for a server with a set of services.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../router/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACT,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAIL,iBAAiB,EAElB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,SAAS,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAElD;;;;GAIG;AACH,KAAK,aAAa,CAAC,MAAM,SAAS,UAAU,IAAI;KAC7C,QAAQ,IAAI,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,QAAQ,CAChD,MAAM,EACN,QAAQ,CACT,SAAS,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../router/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACT,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAIL,iBAAiB,EAElB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,KAAK,SAAS,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAElD;;;;GAIG;AACH,KAAK,aAAa,CAAC,MAAM,SAAS,UAAU,IAAI;KAC7C,QAAQ,IAAI,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,QAAQ,CAChD,MAAM,EACN,QAAQ,CACT,SAAS,KAAK,GACX;QACE,GAAG,EAAE,CACH,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,KACvC,OAAO,CACV,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF,CAAC;KACH,GACD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,QAAQ,GAC3C;QACE,MAAM,EAAE,MAAM,OAAO,CACnB;YACE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7C,SAAS,CACP,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF;YACD,MAAM,IAAI;SACX,CACF,CAAC;KACH,GACD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,cAAc,GACjD;QACE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,OAAO,CAChE;YACE,SAAS,CACP,MAAM,CACJ,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EACpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACrC,CACF;YACD,MAAM,IAAI;SACX,CACF,CAAC;KACH,GACD,KAAK;CACV,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI;KACxE,OAAO,IAAI,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;CAC5E,CAAC;AAgCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,8DACZ,UAAU,UAAU,CAAC,aACtB,iBAAiB,sBAmHA,CAAC"}
|
package/dist/router/client.js
CHANGED
|
@@ -35,7 +35,10 @@ function _createRecursiveProxy(callback, path) {
|
|
|
35
35
|
* @returns The client for the server.
|
|
36
36
|
*/
|
|
37
37
|
export const createClient = (transport, serverId = 'SERVER') => _createRecursiveProxy(async (opts) => {
|
|
38
|
-
const [serviceName, procName] = [...opts.path];
|
|
38
|
+
const [serviceName, procName, procType] = [...opts.path];
|
|
39
|
+
if (!(serviceName && procName && procType)) {
|
|
40
|
+
throw new Error('invalid river call, ensure the service and procedure you are calling exists');
|
|
41
|
+
}
|
|
39
42
|
const [input] = opts.args;
|
|
40
43
|
const streamId = nanoid();
|
|
41
44
|
function belongsToSameStream(msg) {
|
|
@@ -43,8 +46,7 @@ export const createClient = (transport, serverId = 'SERVER') => _createRecursive
|
|
|
43
46
|
msg.procedureName === procName &&
|
|
44
47
|
msg.streamId === streamId);
|
|
45
48
|
}
|
|
46
|
-
if (
|
|
47
|
-
// stream case (stream methods are called with zero arguments)
|
|
49
|
+
if (procType === 'stream') {
|
|
48
50
|
const inputStream = pushable({ objectMode: true });
|
|
49
51
|
const outputStream = pushable({ objectMode: true });
|
|
50
52
|
// input -> transport
|
|
@@ -73,8 +75,7 @@ export const createClient = (transport, serverId = 'SERVER') => _createRecursive
|
|
|
73
75
|
};
|
|
74
76
|
return [inputStream, outputStream, closeHandler];
|
|
75
77
|
}
|
|
76
|
-
else {
|
|
77
|
-
// rpc case
|
|
78
|
+
else if (procType === 'rpc') {
|
|
78
79
|
const m = msg(transport.clientId, serverId, serviceName, procName, streamId, input);
|
|
79
80
|
// rpc is a stream open + close
|
|
80
81
|
m.controlFlags |=
|
|
@@ -82,4 +83,25 @@ export const createClient = (transport, serverId = 'SERVER') => _createRecursive
|
|
|
82
83
|
transport.send(m);
|
|
83
84
|
return waitForMessage(transport, belongsToSameStream);
|
|
84
85
|
}
|
|
86
|
+
else if (procType === 'subscribe') {
|
|
87
|
+
const m = msg(transport.clientId, serverId, serviceName, procName, streamId, input);
|
|
88
|
+
m.controlFlags |= 2 /* ControlFlags.StreamOpenBit */;
|
|
89
|
+
transport.send(m);
|
|
90
|
+
// transport -> output
|
|
91
|
+
const outputStream = pushable({ objectMode: true });
|
|
92
|
+
const listener = (msg) => {
|
|
93
|
+
if (belongsToSameStream(msg)) {
|
|
94
|
+
outputStream.push(msg.payload);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
transport.addMessageListener(listener);
|
|
98
|
+
const closeHandler = () => {
|
|
99
|
+
outputStream.end();
|
|
100
|
+
transport.removeMessageListener(listener);
|
|
101
|
+
};
|
|
102
|
+
return [outputStream, closeHandler];
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
throw new Error(`invalid river call, unknown procedure type ${procType}`);
|
|
106
|
+
}
|
|
85
107
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../router/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAgB,UAAU,EAAE,MAAM,WAAW,CAAC;AAWrD,OAAO,EAAE,cAAc,EAA2B,MAAM,WAAW,CAAC;AAWpE;;;GAGG;AACH,MAAM,WAAW,MAAM,CAAC,QAAQ;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAWD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAC5E,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,QAAQ,EAAE,QAAQ,EAClB,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../router/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAgB,UAAU,EAAE,MAAM,WAAW,CAAC;AAWrD,OAAO,EAAE,cAAc,EAA2B,MAAM,WAAW,CAAC;AAWpE;;;GAGG;AACH,MAAM,WAAW,MAAM,CAAC,QAAQ;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAWD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAC5E,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,QAAQ,EAAE,QAAQ,EAClB,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CA0K3B"}
|
package/dist/router/server.js
CHANGED
|
@@ -44,7 +44,8 @@ export async function createServer(transport, services, extendedContext) {
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
const procedure = service.procedures[msg.procedureName];
|
|
47
|
-
|
|
47
|
+
const streamIdx = `${msg.serviceName}.${msg.procedureName}:${msg.streamId}`;
|
|
48
|
+
if (isStreamOpen(msg.controlFlags) && !streamMap.has(streamIdx)) {
|
|
48
49
|
const incoming = pushable({ objectMode: true });
|
|
49
50
|
const outgoing = pushable({ objectMode: true });
|
|
50
51
|
const openPromises = [
|
|
@@ -71,24 +72,40 @@ export async function createServer(transport, services, extendedContext) {
|
|
|
71
72
|
}
|
|
72
73
|
else if (procedure.type === 'rpc') {
|
|
73
74
|
openPromises.push((async () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const inputMessage = await incoming.next();
|
|
76
|
+
if (inputMessage.done) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const outputMessage = await procedure.handler(serviceContext, inputMessage.value);
|
|
81
|
+
outgoing.push(outputMessage);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
errorHandler(err);
|
|
85
|
+
}
|
|
86
|
+
})());
|
|
87
|
+
}
|
|
88
|
+
else if (procedure.type === 'subscription') {
|
|
89
|
+
openPromises.push((async () => {
|
|
90
|
+
const inputMessage = await incoming.next();
|
|
91
|
+
if (inputMessage.done) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
await procedure.handler(serviceContext, inputMessage.value, outgoing);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
errorHandler(err);
|
|
82
99
|
}
|
|
83
100
|
})());
|
|
84
101
|
}
|
|
85
|
-
streamMap.set(
|
|
102
|
+
streamMap.set(streamIdx, {
|
|
86
103
|
incoming,
|
|
87
104
|
outgoing,
|
|
88
105
|
openPromises,
|
|
89
106
|
});
|
|
90
107
|
}
|
|
91
|
-
const procStream = streamMap.get(
|
|
108
|
+
const procStream = streamMap.get(streamIdx);
|
|
92
109
|
if (!procStream) {
|
|
93
110
|
log?.warn(`${transport.clientId} -- couldn't find a matching procedure stream for ${msg.serviceName}.${msg.procedureName}:${msg.streamId}`);
|
|
94
111
|
return;
|
|
@@ -2,7 +2,7 @@ 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 '../../../
|
|
5
|
+
import { payloadToTransportMessage } from '../../../util/testHelpers';
|
|
6
6
|
describe('sending and receiving across node streams works', () => {
|
|
7
7
|
test('basic send/receive', async () => {
|
|
8
8
|
const clientToServer = new stream.PassThrough();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { describe, test, expect, afterAll } from 'vitest';
|
|
3
|
-
import { createWebSocketServer, createWsTransports, createDummyTransportMessage, onServerReady, createLocalWebSocketClient, } from '../../../
|
|
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';
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import WebSocket from 'isomorphic-ws';
|
|
3
3
|
import { WebSocketServer } from 'ws';
|
|
4
4
|
import http from 'http';
|
|
5
|
-
import { WebSocketClientTransport } from '
|
|
5
|
+
import { WebSocketClientTransport } from '../transport/impls/ws/client';
|
|
6
6
|
import { Static, TObject } from '@sinclair/typebox';
|
|
7
|
-
import { Procedure, ServiceContext } from '
|
|
8
|
-
import { OpaqueTransportMessage, TransportClientId, TransportMessage } from '
|
|
7
|
+
import { Procedure, ServiceContext } from '../router';
|
|
8
|
+
import { OpaqueTransportMessage, TransportClientId, TransportMessage } from '../transport';
|
|
9
9
|
import { Pushable } from 'it-pushable';
|
|
10
|
-
import { Result, RiverError, RiverUncaughtSchema } from '
|
|
11
|
-
import { Codec } from '
|
|
12
|
-
import { WebSocketServerTransport } from '
|
|
10
|
+
import { Result, RiverError, RiverUncaughtSchema } from '../router/result';
|
|
11
|
+
import { Codec } from '../codec';
|
|
12
|
+
import { WebSocketServerTransport } from '../transport/impls/ws/server';
|
|
13
13
|
/**
|
|
14
14
|
* Creates a WebSocket server instance using the provided HTTP server.
|
|
15
15
|
* Only used as helper for testing.
|
|
@@ -61,12 +61,25 @@ export declare function asClientRpc<State extends object | unknown, I extends TO
|
|
|
61
61
|
* @param {State} state - The state object.
|
|
62
62
|
* @param {Procedure<State, 'stream', I, O>} proc - The procedure to handle the stream.
|
|
63
63
|
* @param {Omit<ServiceContext, 'state'>} [extendedContext] - The extended context object.
|
|
64
|
-
* @returns
|
|
64
|
+
* @returns Pair of input and output streams.
|
|
65
65
|
*/
|
|
66
66
|
export declare function asClientStream<State extends object | unknown, I extends TObject, O extends TObject, E extends RiverError>(state: State, proc: Procedure<State, 'stream', I, O, E>, extendedContext?: Omit<ServiceContext, 'state'>): [
|
|
67
67
|
Pushable<Static<I>>,
|
|
68
68
|
Pushable<Result<Static<O>, Static<E> | Static<typeof RiverUncaughtSchema>>>
|
|
69
69
|
];
|
|
70
|
+
/**
|
|
71
|
+
* Transforms a subscription procedure definition into a procedure that returns an output stream.
|
|
72
|
+
* Input messages can be pushed into the input stream.
|
|
73
|
+
* This should only be used for testing.
|
|
74
|
+
* @template State - The type of the state object.
|
|
75
|
+
* @template I - The type of the input object.
|
|
76
|
+
* @template O - The type of the output object.
|
|
77
|
+
* @param {State} state - The state object.
|
|
78
|
+
* @param {Procedure<State, 'stream', I, O>} proc - The procedure to handle the stream.
|
|
79
|
+
* @param {Omit<ServiceContext, 'state'>} [extendedContext] - The extended context object.
|
|
80
|
+
* @returns A function that when passed a message, returns the output stream.
|
|
81
|
+
*/
|
|
82
|
+
export declare function asClientSubscription<State extends object | unknown, I extends TObject, O extends TObject, E extends RiverError>(state: State, proc: Procedure<State, 'subscription', I, O, E>, extendedContext?: Omit<ServiceContext, 'state'>): (msg: Static<I>) => Promise<Pushable<Result<Static<O>, Static<E> | Static<typeof RiverUncaughtSchema>>>>;
|
|
70
83
|
/**
|
|
71
84
|
* Converts a payload object to a transport message with reasonable defaults.
|
|
72
85
|
* This should only be used for testing.
|
|
@@ -80,4 +93,10 @@ export declare function payloadToTransportMessage<Payload extends object>(payloa
|
|
|
80
93
|
* @returns The created opaque transport message.
|
|
81
94
|
*/
|
|
82
95
|
export declare function createDummyTransportMessage(): OpaqueTransportMessage;
|
|
83
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Retrieves the next value from an async iterable iterator.
|
|
98
|
+
* @param iter The async iterable iterator.
|
|
99
|
+
* @returns A promise that resolves to the next value from the iterator.
|
|
100
|
+
*/
|
|
101
|
+
export declare function iterNext<T>(iter: AsyncIterableIterator<T>): Promise<any>;
|
|
102
|
+
//# sourceMappingURL=testHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testHelpers.d.ts","sourceRoot":"","sources":["../../util/testHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import WebSocket from 'isomorphic-ws';
|
|
2
2
|
import { WebSocketServer } from 'ws';
|
|
3
|
-
import { WebSocketClientTransport } from '
|
|
4
|
-
import { msg, reply, } from '
|
|
3
|
+
import { WebSocketClientTransport } from '../transport/impls/ws/client';
|
|
4
|
+
import { msg, reply, } from '../transport';
|
|
5
5
|
import { pushable } from 'it-pushable';
|
|
6
|
-
import { Err, UNCAUGHT_ERROR, } from '
|
|
7
|
-
import { WebSocketServerTransport } from '
|
|
6
|
+
import { Err, UNCAUGHT_ERROR, } from '../router/result';
|
|
7
|
+
import { WebSocketServerTransport } from '../transport/impls/ws/server';
|
|
8
8
|
/**
|
|
9
9
|
* Creates a WebSocket server instance using the provided HTTP server.
|
|
10
10
|
* Only used as helper for testing.
|
|
@@ -91,7 +91,7 @@ export function asClientRpc(state, proc, extendedContext) {
|
|
|
91
91
|
* @param {State} state - The state object.
|
|
92
92
|
* @param {Procedure<State, 'stream', I, O>} proc - The procedure to handle the stream.
|
|
93
93
|
* @param {Omit<ServiceContext, 'state'>} [extendedContext] - The extended context object.
|
|
94
|
-
* @returns
|
|
94
|
+
* @returns Pair of input and output streams.
|
|
95
95
|
*/
|
|
96
96
|
export function asClientStream(state, proc, extendedContext) {
|
|
97
97
|
const rawInput = pushable({ objectMode: true });
|
|
@@ -133,6 +133,44 @@ export function asClientStream(state, proc, extendedContext) {
|
|
|
133
133
|
})();
|
|
134
134
|
return [rawInput, rawOutput];
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Transforms a subscription procedure definition into a procedure that returns an output stream.
|
|
138
|
+
* Input messages can be pushed into the input stream.
|
|
139
|
+
* This should only be used for testing.
|
|
140
|
+
* @template State - The type of the state object.
|
|
141
|
+
* @template I - The type of the input object.
|
|
142
|
+
* @template O - The type of the output object.
|
|
143
|
+
* @param {State} state - The state object.
|
|
144
|
+
* @param {Procedure<State, 'stream', I, O>} proc - The procedure to handle the stream.
|
|
145
|
+
* @param {Omit<ServiceContext, 'state'>} [extendedContext] - The extended context object.
|
|
146
|
+
* @returns A function that when passed a message, returns the output stream.
|
|
147
|
+
*/
|
|
148
|
+
export function asClientSubscription(state, proc, extendedContext) {
|
|
149
|
+
const rawOutput = pushable({
|
|
150
|
+
objectMode: true,
|
|
151
|
+
});
|
|
152
|
+
const transportOutput = pushable({
|
|
153
|
+
objectMode: true,
|
|
154
|
+
});
|
|
155
|
+
// unwrap from transport
|
|
156
|
+
(async () => {
|
|
157
|
+
for await (const transportRes of transportOutput) {
|
|
158
|
+
rawOutput.push(transportRes.payload);
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
return async (msg) => {
|
|
162
|
+
proc
|
|
163
|
+
.handler({ ...extendedContext, state }, payloadToTransportMessage(msg), transportOutput)
|
|
164
|
+
.catch((err) => {
|
|
165
|
+
const errorMsg = err instanceof Error ? err.message : `[coerced to error] ${err}`;
|
|
166
|
+
return Err({
|
|
167
|
+
code: UNCAUGHT_ERROR,
|
|
168
|
+
message: errorMsg,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return rawOutput;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
136
174
|
/**
|
|
137
175
|
* Converts a payload object to a transport message with reasonable defaults.
|
|
138
176
|
* This should only be used for testing.
|
|
@@ -153,3 +191,11 @@ export function createDummyTransportMessage() {
|
|
|
153
191
|
test: Math.random(),
|
|
154
192
|
});
|
|
155
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Retrieves the next value from an async iterable iterator.
|
|
196
|
+
* @param iter The async iterable iterator.
|
|
197
|
+
* @returns A promise that resolves to the next value from the iterator.
|
|
198
|
+
*/
|
|
199
|
+
export function iterNext(iter) {
|
|
200
|
+
return iter.next().then((res) => res.value);
|
|
201
|
+
}
|
package/package.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"name": "@replit/river",
|
|
3
3
|
"sideEffects": false,
|
|
4
4
|
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/router/index.js",
|
|
9
9
|
"./logging": "./dist/logging/index.js",
|
|
10
10
|
"./codec": "./dist/codec/index.js",
|
|
11
|
-
"./test-util": "./dist/
|
|
11
|
+
"./test-util": "./dist/util/testHelpers.js",
|
|
12
12
|
"./transport": "./dist/transport/index.js",
|
|
13
13
|
"./transport/ws/client": "./dist/transport/impls/ws/client.js",
|
|
14
14
|
"./transport/ws/server": "./dist/transport/impls/ws/server.js",
|
|
@@ -1 +0,0 @@
|
|
|
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,4BAA4B;;;;;;;;;;;;;;;;;;;;CAc1B,CAAC;AAEhB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiExB,CAAC"}
|
package/dist/testUtils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../testUtils.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAY,MAAM,aAAa,CAAC;AACjD,OAAO,EAEL,MAAM,EACN,UAAU,EACV,mBAAmB,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEvE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,4EAE9D;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,sBAI5D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,KAAK,CAAC,EAAE,KAAK,GACZ,CAAC,wBAAwB,EAAE,wBAAwB,CAAC,CAWtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACtC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,SAGxC,OAAO,CAAC,CAAC,KACb,QACD,OAAO,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,0BAA0B,CAAC,CAAC,CAClE,CAYF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,SAAS,MAAM,GAAG,OAAO,EAC9B,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,UAAU,EAEpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,eAAe,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAC9C;IACD,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC;CAC5E,CAuDA;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,SAAS,MAAM,EAC9D,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,GAAE,iBAA4B,EAClC,EAAE,GAAE,iBAA4B,GAC/B,gBAAgB,CAAC,OAAO,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,sBAAsB,CAKpE"}
|