@replit/river 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -10
- package/dist/__tests__/integration.test.d.ts +4 -5
- package/dist/codec/codec.test.js +6 -0
- package/dist/codec/json.js +8 -1
- package/dist/codec/types.d.ts +1 -1
- package/dist/logging/index.d.ts +15 -0
- package/dist/logging/index.js +29 -0
- package/dist/router/server.js +6 -3
- package/dist/transport/types.js +12 -4
- package/dist/transport/ws.js +14 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TransportMessage } from '../transport/message';
|
|
2
1
|
export declare const EchoRequest: import("@sinclair/typebox").TObject<{
|
|
3
2
|
msg: import("@sinclair/typebox").TString;
|
|
4
3
|
ignore: import("@sinclair/typebox").TBoolean;
|
|
@@ -21,9 +20,9 @@ export declare const TestServiceConstructor: () => {
|
|
|
21
20
|
}>;
|
|
22
21
|
handler: (context: import("..").ServiceContextWithState<{
|
|
23
22
|
count: number;
|
|
24
|
-
}>, input: TransportMessage<{
|
|
23
|
+
}>, input: import("../transport/message").TransportMessage<{
|
|
25
24
|
n: number;
|
|
26
|
-
}>) => Promise<TransportMessage<{
|
|
25
|
+
}>) => Promise<import("../transport/message").TransportMessage<{
|
|
27
26
|
result: number;
|
|
28
27
|
}>>;
|
|
29
28
|
type: "rpc";
|
|
@@ -39,10 +38,10 @@ export declare const TestServiceConstructor: () => {
|
|
|
39
38
|
}>;
|
|
40
39
|
handler: (context: import("..").ServiceContextWithState<{
|
|
41
40
|
count: number;
|
|
42
|
-
}>, input: AsyncIterable<TransportMessage<{
|
|
41
|
+
}>, input: AsyncIterable<import("../transport/message").TransportMessage<{
|
|
43
42
|
msg: string;
|
|
44
43
|
ignore: boolean;
|
|
45
|
-
}>>, output: import("it-pushable").Pushable<TransportMessage<{
|
|
44
|
+
}>>, output: import("it-pushable").Pushable<import("../transport/message").TransportMessage<{
|
|
46
45
|
response: string;
|
|
47
46
|
}>, void, unknown>) => Promise<void>;
|
|
48
47
|
type: "stream";
|
package/dist/codec/codec.test.js
CHANGED
|
@@ -20,4 +20,10 @@ describe('naive json codec', () => {
|
|
|
20
20
|
};
|
|
21
21
|
expect(NaiveJsonCodec.fromStringBuf(NaiveJsonCodec.toStringBuf(msg))).toStrictEqual(msg);
|
|
22
22
|
});
|
|
23
|
+
test('invalid json returns null', () => {
|
|
24
|
+
expect(NaiveJsonCodec.fromStringBuf('')).toBeNull();
|
|
25
|
+
expect(NaiveJsonCodec.fromStringBuf('[')).toBeNull();
|
|
26
|
+
expect(NaiveJsonCodec.fromStringBuf('[{}')).toBeNull();
|
|
27
|
+
expect(NaiveJsonCodec.fromStringBuf('{"a":1}[]')).toBeNull();
|
|
28
|
+
});
|
|
23
29
|
});
|
package/dist/codec/json.js
CHANGED
package/dist/codec/types.d.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const LoggingLevels: {
|
|
2
|
+
readonly info: 0;
|
|
3
|
+
readonly warn: 1;
|
|
4
|
+
readonly error: 2;
|
|
5
|
+
};
|
|
6
|
+
type LoggingLevel = keyof typeof LoggingLevels;
|
|
7
|
+
export type Logger = {
|
|
8
|
+
minLevel: LoggingLevel;
|
|
9
|
+
} & {
|
|
10
|
+
[key in LoggingLevel]: (msg: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare let log: Logger | undefined;
|
|
13
|
+
export declare function bindLogger(write: (msg: string) => void, color?: boolean): void;
|
|
14
|
+
export declare function setLevel(level: LoggingLevel): void;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const LoggingLevels = {
|
|
2
|
+
info: 0,
|
|
3
|
+
warn: 1,
|
|
4
|
+
error: 2,
|
|
5
|
+
};
|
|
6
|
+
export let log;
|
|
7
|
+
const defaultLoggingLevel = 'warn';
|
|
8
|
+
export function bindLogger(write, color) {
|
|
9
|
+
const info = color ? '\u001b[37minfo\u001b[0m' : 'info';
|
|
10
|
+
const warn = color ? '\u001b[33mwarn\u001b[0m' : 'warn';
|
|
11
|
+
const error = color ? '\u001b[31merr\u001b[0m' : 'err';
|
|
12
|
+
log = {
|
|
13
|
+
info: (msg) => log &&
|
|
14
|
+
LoggingLevels[log.minLevel] <= 0 &&
|
|
15
|
+
write(`[river:${info}] ${msg}`),
|
|
16
|
+
warn: (msg) => log &&
|
|
17
|
+
LoggingLevels[log.minLevel] <= 1 &&
|
|
18
|
+
write(`[river:${warn}] ${msg}`),
|
|
19
|
+
error: (msg) => log &&
|
|
20
|
+
LoggingLevels[log.minLevel] <= 2 &&
|
|
21
|
+
write(`[river:${error}] ${msg}`),
|
|
22
|
+
minLevel: log?.minLevel ?? defaultLoggingLevel,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function setLevel(level) {
|
|
26
|
+
if (log) {
|
|
27
|
+
log.minLevel = level;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/router/server.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { Value } from '@sinclair/typebox/value';
|
|
2
2
|
import { pushable } from 'it-pushable';
|
|
3
|
+
import { log } from '../logging';
|
|
3
4
|
export async function createServer(transport, services, extendedContext) {
|
|
4
5
|
const contextMap = new Map();
|
|
5
6
|
const streamMap = new Map();
|
|
6
7
|
function getContext(service) {
|
|
7
8
|
const context = contextMap.get(service);
|
|
8
9
|
if (!context) {
|
|
9
|
-
|
|
10
|
+
const err = `No context found for ${service.name}`;
|
|
11
|
+
log?.error(err);
|
|
12
|
+
throw new Error(err);
|
|
10
13
|
}
|
|
11
14
|
return context;
|
|
12
15
|
}
|
|
@@ -38,7 +41,6 @@ export async function createServer(transport, services, extendedContext) {
|
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
const handler = async (msg) => {
|
|
41
|
-
// TODO: log msgs received
|
|
42
44
|
if (msg.to !== 'SERVER') {
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
@@ -66,10 +68,11 @@ export async function createServer(transport, services, extendedContext) {
|
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
69
|
-
|
|
71
|
+
log?.error(`${transport.clientId} -- procedure ${msg.serviceName}.${msg.procedureName} received invalid payload: ${inputMessage.payload}`);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
75
|
+
log?.warn(`${transport.clientId} -- couldn't find a matching procedure for ${msg.serviceName}.${msg.procedureName}`);
|
|
73
76
|
};
|
|
74
77
|
transport.addMessageListener(handler);
|
|
75
78
|
return {
|
package/dist/transport/types.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Value } from '@sinclair/typebox/value';
|
|
2
2
|
import { OpaqueTransportMessageSchema, TransportAckSchema, ack, } from './message';
|
|
3
|
+
import { log } from '../logging';
|
|
3
4
|
export class Transport {
|
|
4
5
|
codec;
|
|
5
6
|
clientId;
|
|
@@ -12,15 +13,20 @@ export class Transport {
|
|
|
12
13
|
this.clientId = clientId;
|
|
13
14
|
}
|
|
14
15
|
onMessage(msg) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const parsedMsg = this.codec.fromStringBuf(msg);
|
|
17
|
+
if (parsedMsg === null) {
|
|
18
|
+
log?.warn(`${this.clientId} -- received malformed msg: ${msg}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
17
21
|
if (Value.Check(TransportAckSchema, parsedMsg)) {
|
|
18
22
|
// process ack
|
|
23
|
+
log?.info(`${this.clientId} -- received ack: ${msg}`);
|
|
19
24
|
if (this.sendBuffer.has(parsedMsg.ack)) {
|
|
20
25
|
this.sendBuffer.delete(parsedMsg.ack);
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
else if (Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
29
|
+
log?.info(`${this.clientId} -- received msg: ${msg}`);
|
|
24
30
|
// ignore if not for us
|
|
25
31
|
if (parsedMsg.to !== this.clientId && parsedMsg.to !== 'broadcast') {
|
|
26
32
|
return;
|
|
@@ -29,10 +35,12 @@ export class Transport {
|
|
|
29
35
|
for (const handler of this.handlers) {
|
|
30
36
|
handler(parsedMsg);
|
|
31
37
|
}
|
|
32
|
-
|
|
38
|
+
const ackMsg = ack(parsedMsg);
|
|
39
|
+
ackMsg.from = this.clientId;
|
|
40
|
+
this.send(ackMsg);
|
|
33
41
|
}
|
|
34
42
|
else {
|
|
35
|
-
|
|
43
|
+
log?.warn(`${this.clientId} -- received invalid transport msg: ${msg}`);
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
addMessageListener(handler) {
|
package/dist/transport/ws.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Transport } from './types';
|
|
2
2
|
import { NaiveJsonCodec } from '../codec/json';
|
|
3
|
+
import { log } from '../logging';
|
|
3
4
|
const defaultOptions = {
|
|
4
5
|
retryIntervalMs: 250,
|
|
5
6
|
};
|
|
@@ -22,6 +23,7 @@ export class WebSocketTransport extends Transport {
|
|
|
22
23
|
async tryConnect() {
|
|
23
24
|
// wait until it's ready or we get an error
|
|
24
25
|
this.reconnectPromise ??= new Promise(async (resolve) => {
|
|
26
|
+
log?.info(`${this.clientId} -- establishing a new websocket`);
|
|
25
27
|
const ws = await this.wsGetter();
|
|
26
28
|
if (ws.readyState === ws.OPEN) {
|
|
27
29
|
return resolve({ ws });
|
|
@@ -45,6 +47,7 @@ export class WebSocketTransport extends Transport {
|
|
|
45
47
|
const res = await this.reconnectPromise;
|
|
46
48
|
// only send if we resolved a valid websocket
|
|
47
49
|
if ('ws' in res && res.ws.readyState === res.ws.OPEN) {
|
|
50
|
+
log?.info(`${this.clientId} -- websocket ok`);
|
|
48
51
|
this.ws = res.ws;
|
|
49
52
|
this.ws.onmessage = (msg) => this.onMessage(msg.data.toString());
|
|
50
53
|
this.ws.onclose = () => {
|
|
@@ -55,33 +58,42 @@ export class WebSocketTransport extends Transport {
|
|
|
55
58
|
for (const id of this.sendQueue) {
|
|
56
59
|
const msg = this.sendBuffer.get(id);
|
|
57
60
|
if (!msg) {
|
|
58
|
-
|
|
61
|
+
const err = 'tried to resend a message we received an ack for';
|
|
62
|
+
log?.error(err);
|
|
63
|
+
throw new Error(err);
|
|
59
64
|
}
|
|
65
|
+
log?.info(`${this.clientId} -- sending ${JSON.stringify(msg)}`);
|
|
60
66
|
this.ws.send(this.codec.toStringBuf(msg));
|
|
61
67
|
}
|
|
62
68
|
this.sendQueue = [];
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
// otherwise try and reconnect again
|
|
72
|
+
log?.warn(`${this.clientId} -- websocket failed, trying again in ${this.options.retryIntervalMs}ms`);
|
|
66
73
|
this.reconnectPromise = undefined;
|
|
67
74
|
setTimeout(() => this.tryConnect(), this.options.retryIntervalMs);
|
|
68
75
|
}
|
|
69
76
|
send(msg) {
|
|
70
77
|
const id = msg.id;
|
|
71
78
|
if (this.destroyed) {
|
|
72
|
-
|
|
79
|
+
const err = 'ws is destroyed, cant send';
|
|
80
|
+
log?.error(err);
|
|
81
|
+
throw new Error(err);
|
|
73
82
|
}
|
|
74
83
|
this.sendBuffer.set(id, msg);
|
|
75
84
|
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
85
|
+
log?.info(`${this.clientId} -- sending ${JSON.stringify(msg)}`);
|
|
76
86
|
this.ws.send(this.codec.toStringBuf(msg));
|
|
77
87
|
}
|
|
78
88
|
else {
|
|
89
|
+
log?.info(`${this.clientId} -- transport not ready, queuing ${JSON.stringify(msg)}`);
|
|
79
90
|
this.sendQueue.push(id);
|
|
80
91
|
this.tryConnect().catch();
|
|
81
92
|
}
|
|
82
93
|
return id;
|
|
83
94
|
}
|
|
84
95
|
async close() {
|
|
96
|
+
log?.info('manually closed ws');
|
|
85
97
|
this.destroyed = true;
|
|
86
98
|
return this.ws?.close();
|
|
87
99
|
}
|
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
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.2.
|
|
5
|
+
"version": "0.2.2",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "index.js",
|
|
8
|
-
"types": "index.d.ts",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|