starpc 0.1.7 → 0.2.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/Makefile +1 -0
- package/README.md +24 -5
- package/dist/echo/client-test.d.ts +2 -0
- package/dist/echo/client-test.js +35 -0
- package/dist/echo/echo.d.ts +124 -0
- package/dist/echo/echo.js +42 -0
- package/dist/echo/index.d.ts +3 -0
- package/dist/echo/index.js +3 -0
- package/dist/echo/server.d.ts +8 -0
- package/dist/echo/server.js +50 -0
- package/dist/echo/sever.d.ts +0 -0
- package/dist/echo/sever.js +1 -0
- package/dist/srpc/broadcast-channel.d.ts +3 -2
- package/dist/srpc/broadcast-channel.js +2 -2
- package/dist/srpc/client-rpc.d.ts +4 -28
- package/dist/srpc/client-rpc.js +9 -152
- package/dist/srpc/client.d.ts +2 -2
- package/dist/srpc/client.js +56 -84
- package/dist/srpc/common-rpc.d.ts +24 -0
- package/dist/srpc/common-rpc.js +140 -0
- package/dist/srpc/conn.d.ts +8 -3
- package/dist/srpc/conn.js +19 -3
- package/dist/srpc/definition.d.ts +15 -0
- package/dist/srpc/definition.js +1 -0
- package/dist/srpc/handler.d.ts +24 -0
- package/dist/srpc/handler.js +123 -0
- package/dist/srpc/index.d.ts +7 -3
- package/dist/srpc/index.js +6 -2
- package/dist/srpc/message.d.ts +11 -0
- package/dist/srpc/message.js +32 -0
- package/dist/srpc/mux.d.ts +11 -0
- package/dist/srpc/mux.js +36 -0
- package/dist/srpc/packet.d.ts +4 -4
- package/dist/srpc/packet.js +38 -27
- package/dist/srpc/rpcproto.d.ts +18 -43
- package/dist/srpc/rpcproto.js +37 -78
- package/dist/srpc/server-rpc.d.ts +11 -0
- package/dist/srpc/server-rpc.js +55 -0
- package/dist/srpc/server.d.ts +14 -0
- package/dist/srpc/server.js +31 -0
- package/dist/srpc/websocket.d.ts +3 -2
- package/dist/srpc/websocket.js +4 -4
- package/e2e/README.md +10 -0
- package/e2e/e2e.ts +27 -0
- package/echo/client-test.ts +41 -0
- package/echo/echo.ts +45 -0
- package/echo/index.ts +3 -0
- package/echo/server.ts +57 -0
- package/echo/sever.ts +0 -0
- package/integration/integration.bash +1 -1
- package/integration/integration.ts +10 -42
- package/package.json +18 -17
- package/srpc/broadcast-channel.ts +8 -3
- package/srpc/client-rpc.go +1 -9
- package/srpc/client-rpc.ts +11 -175
- package/srpc/client.ts +58 -99
- package/srpc/common-rpc.ts +171 -0
- package/srpc/conn.ts +33 -5
- package/srpc/definition.ts +30 -0
- package/srpc/handler.ts +174 -0
- package/srpc/index.ts +7 -3
- package/srpc/message.ts +60 -0
- package/srpc/mux.ts +56 -0
- package/srpc/packet.go +4 -11
- package/srpc/packet.ts +44 -30
- package/srpc/rpcproto.pb.go +54 -118
- package/srpc/rpcproto.proto +7 -12
- package/srpc/rpcproto.ts +38 -101
- package/srpc/rpcproto_vtproto.pb.go +58 -210
- package/srpc/server-rpc.go +5 -10
- package/srpc/server-rpc.ts +65 -0
- package/srpc/server.ts +56 -0
- package/srpc/websocket.ts +6 -4
package/dist/srpc/client.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
import { from as observableFrom } from 'rxjs';
|
|
2
|
-
import { ClientRPC } from './client-rpc';
|
|
3
1
|
import { pipe } from 'it-pipe';
|
|
4
2
|
import { pushable } from 'it-pushable';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return async (data) => {
|
|
9
|
-
// resolve the promise
|
|
10
|
-
resolve(data);
|
|
11
|
-
// this is the last data we expect.
|
|
12
|
-
return false;
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
// streamingDataCb builds a new streaming request data callback.
|
|
16
|
-
/*
|
|
17
|
-
function streamingDataCb(resolve: (data: Uint8Array) => void): DataCb {
|
|
18
|
-
return async (
|
|
19
|
-
data: Uint8Array
|
|
20
|
-
): Promise<boolean | void> => {
|
|
21
|
-
// TODO
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
*/
|
|
3
|
+
import { from as observableFrom } from 'rxjs';
|
|
4
|
+
import { ClientRPC } from './client-rpc.js';
|
|
5
|
+
import { decodePacketSource, encodePacketSource, parseLengthPrefixTransform, prependLengthPrefixTransform, } from './packet.js';
|
|
25
6
|
// writeClientStream registers the subscriber to write the client data stream.
|
|
26
7
|
function writeClientStream(call, data) {
|
|
27
8
|
data.subscribe({
|
|
@@ -32,20 +13,10 @@ function writeClientStream(call, data) {
|
|
|
32
13
|
call.close(err);
|
|
33
14
|
},
|
|
34
15
|
complete() {
|
|
35
|
-
call.writeCallData(
|
|
16
|
+
call.writeCallData(undefined, true);
|
|
36
17
|
},
|
|
37
18
|
});
|
|
38
19
|
}
|
|
39
|
-
// waitCallComplete handles the call complete promise.
|
|
40
|
-
function waitCallComplete(call, resolve, reject) {
|
|
41
|
-
call
|
|
42
|
-
.waitComplete()
|
|
43
|
-
.catch(reject)
|
|
44
|
-
.finally(() => {
|
|
45
|
-
// ensure we resolve it if no data was ever returned.
|
|
46
|
-
resolve(new Uint8Array());
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
20
|
// Client implements the ts-proto Rpc interface with the drpcproto protocol.
|
|
50
21
|
export class Client {
|
|
51
22
|
// openConnFn is the open connection function.
|
|
@@ -56,47 +27,42 @@ export class Client {
|
|
|
56
27
|
}
|
|
57
28
|
// request starts a non-streaming request.
|
|
58
29
|
async request(service, method, data) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
const call = await this.startRpc(service, method, data);
|
|
31
|
+
for await (const data of call.rpcDataSource) {
|
|
32
|
+
call.close();
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
const err = new Error('empty response');
|
|
36
|
+
call.close(err);
|
|
37
|
+
throw err;
|
|
67
38
|
}
|
|
68
39
|
// clientStreamingRequest starts a client side streaming request.
|
|
69
|
-
clientStreamingRequest(service, method, data) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
40
|
+
async clientStreamingRequest(service, method, data) {
|
|
41
|
+
const call = await this.startRpc(service, method, null);
|
|
42
|
+
writeClientStream(call, data);
|
|
43
|
+
for await (const data of call.rpcDataSource) {
|
|
44
|
+
call.close();
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
const err = new Error('empty response');
|
|
48
|
+
call.close(err);
|
|
49
|
+
throw err;
|
|
79
50
|
}
|
|
80
51
|
// serverStreamingRequest starts a server-side streaming request.
|
|
81
52
|
serverStreamingRequest(service, method, data) {
|
|
82
53
|
const pushServerData = pushable();
|
|
83
54
|
const serverData = observableFrom(pushServerData);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
call
|
|
93
|
-
.waitComplete()
|
|
94
|
-
.catch((err) => {
|
|
55
|
+
this.startRpc(service, method, data)
|
|
56
|
+
.then(async (call) => {
|
|
57
|
+
try {
|
|
58
|
+
for await (const data of call.rpcDataSource) {
|
|
59
|
+
pushServerData.push(data);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
95
63
|
pushServerData.throw(err);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
pushServerData.end();
|
|
99
|
-
});
|
|
64
|
+
}
|
|
65
|
+
pushServerData.end();
|
|
100
66
|
})
|
|
101
67
|
.catch(pushServerData.throw.bind(pushServerData));
|
|
102
68
|
return serverData;
|
|
@@ -105,32 +71,38 @@ export class Client {
|
|
|
105
71
|
bidirectionalStreamingRequest(service, method, data) {
|
|
106
72
|
const pushServerData = pushable();
|
|
107
73
|
const serverData = observableFrom(pushServerData);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
74
|
+
this.startRpc(service, method, null)
|
|
75
|
+
.then(async (call) => {
|
|
76
|
+
try {
|
|
77
|
+
data.subscribe({
|
|
78
|
+
next(value) {
|
|
79
|
+
call.writeCallData(value);
|
|
80
|
+
},
|
|
81
|
+
error(err) {
|
|
82
|
+
call.close(err);
|
|
83
|
+
},
|
|
84
|
+
complete() {
|
|
85
|
+
call.close();
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
for await (const data of call.rpcDataSource) {
|
|
89
|
+
pushServerData.push(data);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
120
93
|
pushServerData.throw(err);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
pushServerData.end();
|
|
124
|
-
});
|
|
94
|
+
}
|
|
95
|
+
pushServerData.end();
|
|
125
96
|
})
|
|
126
97
|
.catch(pushServerData.throw.bind(pushServerData));
|
|
127
98
|
return serverData;
|
|
128
99
|
}
|
|
129
100
|
// startRpc is a common utility function to begin a rpc call.
|
|
130
101
|
// throws any error starting the rpc call
|
|
131
|
-
|
|
102
|
+
// if data == null and data.length == 0, sends a separate data packet.
|
|
103
|
+
async startRpc(rpcService, rpcMethod, data) {
|
|
132
104
|
const conn = await this.openConnFn();
|
|
133
|
-
const call = new ClientRPC(rpcService, rpcMethod
|
|
105
|
+
const call = new ClientRPC(rpcService, rpcMethod);
|
|
134
106
|
pipe(conn, parseLengthPrefixTransform(), decodePacketSource, call, encodePacketSource, prependLengthPrefixTransform(), conn);
|
|
135
107
|
await call.writeCallStart(data || undefined);
|
|
136
108
|
return call;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Source, Sink } from 'it-stream-types';
|
|
2
|
+
import type { CallData, CallStart } from './rpcproto.js';
|
|
3
|
+
import { Packet } from './rpcproto.js';
|
|
4
|
+
export declare class CommonRPC {
|
|
5
|
+
sink: Sink<Packet>;
|
|
6
|
+
source: AsyncIterable<Packet>;
|
|
7
|
+
private readonly _source;
|
|
8
|
+
readonly rpcDataSource: Source<Uint8Array>;
|
|
9
|
+
private readonly _rpcDataSource;
|
|
10
|
+
protected service?: string;
|
|
11
|
+
protected method?: string;
|
|
12
|
+
constructor();
|
|
13
|
+
writeCallData(data?: Uint8Array, complete?: boolean, error?: string): Promise<void>;
|
|
14
|
+
protected writePacket(packet: Packet): Promise<void>;
|
|
15
|
+
handleMessage(message: Uint8Array): Promise<void>;
|
|
16
|
+
handlePacket(packet: Partial<Packet>): Promise<void>;
|
|
17
|
+
handleCallStart(packet: Partial<CallStart>): Promise<void>;
|
|
18
|
+
protected pushRpcData(data: Uint8Array | undefined, dataIsZero: boolean | undefined): void;
|
|
19
|
+
handleCallData(packet: Partial<CallData>): Promise<void>;
|
|
20
|
+
close(err?: Error): Promise<void>;
|
|
21
|
+
private _createSink;
|
|
22
|
+
private _createSource;
|
|
23
|
+
private _createRpcDataSource;
|
|
24
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { pushable } from 'it-pushable';
|
|
2
|
+
import { Packet } from './rpcproto.js';
|
|
3
|
+
// CommonRPC is common logic between server and client RPCs.
|
|
4
|
+
export class CommonRPC {
|
|
5
|
+
// sink is the data sink for incoming messages.
|
|
6
|
+
sink;
|
|
7
|
+
// source is the packet source for outgoing Packets.
|
|
8
|
+
source;
|
|
9
|
+
// _source is used to write to the source.
|
|
10
|
+
_source;
|
|
11
|
+
// rpcDataSource emits incoming client RPC messages to the caller.
|
|
12
|
+
rpcDataSource;
|
|
13
|
+
// _rpcDataSource is used to write to the rpc message source.
|
|
14
|
+
_rpcDataSource;
|
|
15
|
+
// service is the rpc service
|
|
16
|
+
service;
|
|
17
|
+
// method is the rpc method
|
|
18
|
+
method;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.sink = this._createSink();
|
|
21
|
+
const sourcev = this._createSource();
|
|
22
|
+
this.source = sourcev;
|
|
23
|
+
this._source = sourcev;
|
|
24
|
+
const rpcDataSource = this._createRpcDataSource();
|
|
25
|
+
this.rpcDataSource = rpcDataSource;
|
|
26
|
+
this._rpcDataSource = rpcDataSource;
|
|
27
|
+
}
|
|
28
|
+
// writeCallData writes the call data packet.
|
|
29
|
+
async writeCallData(data, complete, error) {
|
|
30
|
+
const callData = {
|
|
31
|
+
data: data || new Uint8Array(0),
|
|
32
|
+
dataIsZero: !!data && data.length === 0,
|
|
33
|
+
complete: complete || false,
|
|
34
|
+
error: error || '',
|
|
35
|
+
};
|
|
36
|
+
await this.writePacket({
|
|
37
|
+
body: {
|
|
38
|
+
$case: 'callData',
|
|
39
|
+
callData,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// writePacket writes a packet to the stream.
|
|
44
|
+
async writePacket(packet) {
|
|
45
|
+
this._source.push(packet);
|
|
46
|
+
}
|
|
47
|
+
// handleMessage handles an incoming encoded Packet.
|
|
48
|
+
//
|
|
49
|
+
// note: closes the stream if any error is thrown.
|
|
50
|
+
async handleMessage(message) {
|
|
51
|
+
return this.handlePacket(Packet.decode(message));
|
|
52
|
+
}
|
|
53
|
+
// handlePacket handles an incoming packet.
|
|
54
|
+
//
|
|
55
|
+
// note: closes the stream if any error is thrown.
|
|
56
|
+
async handlePacket(packet) {
|
|
57
|
+
try {
|
|
58
|
+
switch (packet?.body?.$case) {
|
|
59
|
+
case 'callStart':
|
|
60
|
+
await this.handleCallStart(packet.body.callStart);
|
|
61
|
+
break;
|
|
62
|
+
case 'callData':
|
|
63
|
+
await this.handleCallData(packet.body.callData);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
let asError = err;
|
|
69
|
+
if (!asError?.message) {
|
|
70
|
+
asError = new Error('error handling packet');
|
|
71
|
+
}
|
|
72
|
+
this.close(asError);
|
|
73
|
+
throw asError;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// handleCallStart handles a CallStart packet.
|
|
77
|
+
async handleCallStart(packet) {
|
|
78
|
+
// no-op
|
|
79
|
+
throw new Error(`unexpected call start: ${packet.rpcService}/${packet.rpcMethod}`);
|
|
80
|
+
}
|
|
81
|
+
// pushRpcData pushes incoming rpc data to the rpc data source.
|
|
82
|
+
pushRpcData(data, dataIsZero) {
|
|
83
|
+
if (dataIsZero) {
|
|
84
|
+
if (!data || data.length !== 0) {
|
|
85
|
+
data = new Uint8Array(0);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (!data || data.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this._rpcDataSource.push(data);
|
|
92
|
+
}
|
|
93
|
+
// handleCallData handles a CallData packet.
|
|
94
|
+
async handleCallData(packet) {
|
|
95
|
+
if (!this.service || !this.method) {
|
|
96
|
+
throw new Error('call start must be sent before call data');
|
|
97
|
+
}
|
|
98
|
+
this.pushRpcData(packet.data, packet.dataIsZero);
|
|
99
|
+
if (packet.error) {
|
|
100
|
+
this._rpcDataSource.end(new Error(packet.error));
|
|
101
|
+
}
|
|
102
|
+
else if (packet.complete) {
|
|
103
|
+
this._rpcDataSource.end();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// close marks the call as complete, optionally with an error.
|
|
107
|
+
async close(err) {
|
|
108
|
+
this._rpcDataSource.end(err);
|
|
109
|
+
this._source.end(err);
|
|
110
|
+
}
|
|
111
|
+
// _createSink returns a value for the sink field.
|
|
112
|
+
_createSink() {
|
|
113
|
+
return async (source) => {
|
|
114
|
+
try {
|
|
115
|
+
for await (const msg of source) {
|
|
116
|
+
await this.handlePacket(msg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const anyErr = err;
|
|
121
|
+
if (anyErr?.code !== 'ERR_MPLEX_STREAM_RESET') {
|
|
122
|
+
this.close(err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
this._rpcDataSource.end();
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// _createSource returns a value for the source field.
|
|
129
|
+
_createSource() {
|
|
130
|
+
return pushable({
|
|
131
|
+
objectMode: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// _createRpcDataSource returns a value for the rpc data source field.
|
|
135
|
+
_createRpcDataSource() {
|
|
136
|
+
return pushable({
|
|
137
|
+
objectMode: true,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
package/dist/srpc/conn.d.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import type { Stream } from '@libp2p/interface-connection';
|
|
2
2
|
import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer';
|
|
3
3
|
import type { Duplex } from 'it-stream-types';
|
|
4
|
-
import type { Stream as SRPCStream } from './stream';
|
|
5
|
-
import { Client } from './client';
|
|
4
|
+
import type { OpenStreamFunc, Stream as SRPCStream } from './stream.js';
|
|
5
|
+
import { Client } from './client.js';
|
|
6
6
|
export interface ConnParams {
|
|
7
7
|
muxerFactory?: StreamMuxerFactory;
|
|
8
8
|
}
|
|
9
|
+
export interface StreamHandler {
|
|
10
|
+
handleStream(strm: Duplex<Uint8Array>): Promise<void>;
|
|
11
|
+
}
|
|
9
12
|
export declare class Conn implements Duplex<Uint8Array> {
|
|
10
13
|
private muxer;
|
|
11
|
-
|
|
14
|
+
private server?;
|
|
15
|
+
constructor(server?: StreamHandler, connParams?: ConnParams);
|
|
12
16
|
get sink(): import("it-stream-types").Sink<Uint8Array, Promise<void>>;
|
|
13
17
|
get source(): import("it-stream-types").Source<Uint8Array>;
|
|
14
18
|
get streams(): Stream[];
|
|
15
19
|
buildClient(): Client;
|
|
16
20
|
openStream(): Promise<SRPCStream>;
|
|
21
|
+
buildOpenStreamFunc(): OpenStreamFunc;
|
|
17
22
|
private handleIncomingStream;
|
|
18
23
|
}
|
package/dist/srpc/conn.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { Mplex } from '@libp2p/mplex';
|
|
2
|
-
import { Client } from './client';
|
|
2
|
+
import { Client } from './client.js';
|
|
3
3
|
// Conn implements a generic connection with a two-way stream.
|
|
4
|
+
// Implements the client by opening streams with the remote.
|
|
5
|
+
// Implements the server by handling incoming streams.
|
|
6
|
+
// If the server is unset, rejects any incoming streams.
|
|
4
7
|
export class Conn {
|
|
5
8
|
// muxer is the mplex stream muxer.
|
|
6
9
|
muxer;
|
|
7
|
-
|
|
10
|
+
// server is the server side, if set.
|
|
11
|
+
server;
|
|
12
|
+
constructor(server, connParams) {
|
|
13
|
+
if (server) {
|
|
14
|
+
this.server = server;
|
|
15
|
+
}
|
|
8
16
|
let muxerFactory = connParams?.muxerFactory;
|
|
9
17
|
if (!muxerFactory) {
|
|
10
18
|
muxerFactory = new Mplex();
|
|
@@ -33,8 +41,16 @@ export class Conn {
|
|
|
33
41
|
async openStream() {
|
|
34
42
|
return this.muxer.newStream();
|
|
35
43
|
}
|
|
44
|
+
// buildOpenStreamFunc returns openStream bound to this conn.
|
|
45
|
+
buildOpenStreamFunc() {
|
|
46
|
+
return this.openStream.bind(this);
|
|
47
|
+
}
|
|
36
48
|
// handleIncomingStream handles an incoming stream.
|
|
37
49
|
handleIncomingStream(strm) {
|
|
38
|
-
|
|
50
|
+
const server = this.server;
|
|
51
|
+
if (!server) {
|
|
52
|
+
return strm.abort(new Error('server not implemented'));
|
|
53
|
+
}
|
|
54
|
+
server.handleStream(strm);
|
|
39
55
|
}
|
|
40
56
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MessageDefinition } from './message';
|
|
2
|
+
export interface Definition {
|
|
3
|
+
name: string;
|
|
4
|
+
fullName: string;
|
|
5
|
+
methods: {
|
|
6
|
+
[id: string]: MethodDefinition<unknown, unknown>;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface MethodDefinition<RequestType, ResponseType> {
|
|
10
|
+
name: string;
|
|
11
|
+
requestType: MessageDefinition<RequestType>;
|
|
12
|
+
requestStream: boolean;
|
|
13
|
+
responseType: MessageDefinition<ResponseType>;
|
|
14
|
+
responseStream: boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Sink, Source } from 'it-stream-types';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { Definition, MethodDefinition } from './definition.js';
|
|
4
|
+
export declare type InvokeFn = (dataSource: Source<Uint8Array>, dataSink: Sink<Uint8Array>) => Promise<void>;
|
|
5
|
+
export interface Handler {
|
|
6
|
+
getServiceID(): string;
|
|
7
|
+
getMethodIDs(): string[];
|
|
8
|
+
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare type MethodMap = {
|
|
11
|
+
[name: string]: InvokeFn;
|
|
12
|
+
};
|
|
13
|
+
export declare class StaticHandler implements Handler {
|
|
14
|
+
private service;
|
|
15
|
+
private methods;
|
|
16
|
+
constructor(serviceID: string, methods: MethodMap);
|
|
17
|
+
getServiceID(): string;
|
|
18
|
+
getMethodIDs(): string[];
|
|
19
|
+
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
20
|
+
}
|
|
21
|
+
declare type MethodProto = ((request: unknown) => Promise<unknown>) | ((request: unknown) => Observable<unknown>) | ((request: Observable<unknown>) => Promise<unknown>) | ((request: Observable<unknown>) => Observable<unknown>);
|
|
22
|
+
export declare function createInvokeFn(methodInfo: MethodDefinition<unknown, unknown>, methodProto: MethodProto): InvokeFn;
|
|
23
|
+
export declare function createHandler(definition: Definition, impl: any): Handler;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { pipe } from 'it-pipe';
|
|
2
|
+
import { pushable } from 'it-pushable';
|
|
3
|
+
import { from as observableFrom } from 'rxjs';
|
|
4
|
+
import { buildDecodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
|
|
5
|
+
// StaticHandler is a handler with a definition and implementation.
|
|
6
|
+
export class StaticHandler {
|
|
7
|
+
// service is the service id
|
|
8
|
+
service;
|
|
9
|
+
// methods is the map of method to invoke fn
|
|
10
|
+
methods;
|
|
11
|
+
constructor(serviceID, methods) {
|
|
12
|
+
this.service = serviceID;
|
|
13
|
+
this.methods = methods;
|
|
14
|
+
}
|
|
15
|
+
// getServiceID returns the ID of the service.
|
|
16
|
+
getServiceID() {
|
|
17
|
+
return this.service;
|
|
18
|
+
}
|
|
19
|
+
// getMethodIDs returns the IDs of the methods.
|
|
20
|
+
getMethodIDs() {
|
|
21
|
+
return Object.keys(this.methods);
|
|
22
|
+
}
|
|
23
|
+
// lookupMethod looks up the method matching the service & method ID.
|
|
24
|
+
// returns null if not found.
|
|
25
|
+
async lookupMethod(serviceID, methodID) {
|
|
26
|
+
if (serviceID && serviceID !== this.service) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return this.methods[methodID] || null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// createInvokeFn builds an InvokeFn from a method definition and a function prototype.
|
|
33
|
+
export function createInvokeFn(methodInfo, methodProto) {
|
|
34
|
+
const requestDecode = buildDecodeMessageTransform(methodInfo.requestType);
|
|
35
|
+
return async (dataSource, dataSink) => {
|
|
36
|
+
// responseSink is a Sink for response messages.
|
|
37
|
+
const responseSink = pushable({
|
|
38
|
+
objectMode: true,
|
|
39
|
+
});
|
|
40
|
+
// pipe responseSink to dataSink.
|
|
41
|
+
const responsePipe = pipe(responseSink, buildEncodeMessageTransform(methodInfo.responseType), dataSink);
|
|
42
|
+
// build the request argument.
|
|
43
|
+
let requestArg;
|
|
44
|
+
if (methodInfo.requestStream) {
|
|
45
|
+
// requestSource is a Source of decoded request messages.
|
|
46
|
+
const requestSource = pipe(dataSource, requestDecode);
|
|
47
|
+
// convert the request data source into an Observable<T>
|
|
48
|
+
requestArg = observableFrom(requestSource);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// receive a single message for the argument.
|
|
52
|
+
const requestRx = requestDecode(dataSource);
|
|
53
|
+
for await (const msg of requestRx) {
|
|
54
|
+
requestArg = msg;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Call the implementation.
|
|
59
|
+
try {
|
|
60
|
+
const responseObj = methodProto(requestArg);
|
|
61
|
+
if (!responseObj) {
|
|
62
|
+
throw new Error('return value was undefined');
|
|
63
|
+
}
|
|
64
|
+
if (methodInfo.responseStream) {
|
|
65
|
+
const responseObs = responseObj;
|
|
66
|
+
if (!responseObs.subscribe) {
|
|
67
|
+
throw new Error('expected return value to be an Observable');
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
responseObs.subscribe({
|
|
71
|
+
next(value) {
|
|
72
|
+
responseSink.push(value);
|
|
73
|
+
},
|
|
74
|
+
error: (err) => {
|
|
75
|
+
responseSink.throw(err);
|
|
76
|
+
reject(err);
|
|
77
|
+
},
|
|
78
|
+
complete: () => {
|
|
79
|
+
responseSink.end();
|
|
80
|
+
responsePipe.finally(() => {
|
|
81
|
+
resolve();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const responsePromise = responseObj;
|
|
89
|
+
if (!responsePromise.then) {
|
|
90
|
+
throw new Error('expected return value to be a Promise');
|
|
91
|
+
}
|
|
92
|
+
const responseMsg = await responsePromise;
|
|
93
|
+
if (!responseMsg) {
|
|
94
|
+
throw new Error('expected non-empty response object');
|
|
95
|
+
}
|
|
96
|
+
responseSink.push(responseMsg);
|
|
97
|
+
responseSink.end();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
let asError = err;
|
|
102
|
+
if (!asError?.message) {
|
|
103
|
+
asError = new Error('error calling implementation: ' + err);
|
|
104
|
+
}
|
|
105
|
+
// mux will return the error to the rpc caller.
|
|
106
|
+
throw asError;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// createHandler creates a handler from a definition and an implementation.
|
|
111
|
+
export function createHandler(definition, impl) {
|
|
112
|
+
const methodMap = {};
|
|
113
|
+
for (const methodInfo of Object.values(definition.methods)) {
|
|
114
|
+
const methodName = methodInfo.name;
|
|
115
|
+
let methodProto = impl[methodName];
|
|
116
|
+
if (!methodProto) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
methodProto = methodProto.bind(impl);
|
|
120
|
+
methodMap[methodName] = createInvokeFn(methodInfo, methodProto);
|
|
121
|
+
}
|
|
122
|
+
return new StaticHandler(definition.fullName, methodMap);
|
|
123
|
+
}
|
package/dist/srpc/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
1
|
+
export type { OpenStreamFunc } from './stream.js';
|
|
2
|
+
export { Client } from './client.js';
|
|
3
|
+
export { Server } from './server.js';
|
|
4
|
+
export { Conn } from './conn.js';
|
|
5
|
+
export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js';
|
|
6
|
+
export { Mux, createMux } from './mux.js';
|
|
7
|
+
export { WebSocketConn } from './websocket.js';
|
package/dist/srpc/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export { Client } from './client';
|
|
2
|
-
export {
|
|
1
|
+
export { Client } from './client.js';
|
|
2
|
+
export { Server } from './server.js';
|
|
3
|
+
export { Conn } from './conn.js';
|
|
4
|
+
export { createHandler, createInvokeFn } from './handler.js';
|
|
5
|
+
export { createMux } from './mux.js';
|
|
6
|
+
export { WebSocketConn } from './websocket.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as pbjs from 'protobufjs/minimal';
|
|
2
|
+
import type { Source } from 'it-stream-types';
|
|
3
|
+
export interface MessageDefinition<T> {
|
|
4
|
+
encode(message: T, writer?: pbjs.Writer): pbjs.Writer;
|
|
5
|
+
decode(input: pbjs.Reader | Uint8Array, length?: number): T;
|
|
6
|
+
}
|
|
7
|
+
declare type DecodeMessageTransform<T> = (source: Source<Uint8Array | Uint8Array[]>) => AsyncIterable<T>;
|
|
8
|
+
export declare function buildDecodeMessageTransform<T>(def: MessageDefinition<T>): DecodeMessageTransform<T>;
|
|
9
|
+
declare type EncodeMessageTransform<T> = (source: Source<T | T[]>) => AsyncIterable<Uint8Array>;
|
|
10
|
+
export declare function buildEncodeMessageTransform<T>(def: MessageDefinition<T>): EncodeMessageTransform<T>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// buildDecodeMessageTransform builds a source of decoded messages.
|
|
2
|
+
export function buildDecodeMessageTransform(def) {
|
|
3
|
+
// decodeMessageSource unmarshals and async yields encoded Messages.
|
|
4
|
+
return async function* decodeMessageSource(source) {
|
|
5
|
+
for await (const pkt of source) {
|
|
6
|
+
if (Array.isArray(pkt)) {
|
|
7
|
+
for (const p of pkt) {
|
|
8
|
+
yield* [def.decode(p)];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
yield* [def.decode(pkt)];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// buildEncodeMessageTransform builds a source of decoded messages.
|
|
18
|
+
export function buildEncodeMessageTransform(def) {
|
|
19
|
+
// encodeMessageSource encodes messages to byte arrays.
|
|
20
|
+
return async function* encodeMessageSource(source) {
|
|
21
|
+
for await (const pkt of source) {
|
|
22
|
+
if (Array.isArray(pkt)) {
|
|
23
|
+
for (const p of pkt) {
|
|
24
|
+
yield* [def.encode(p).finish()];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
yield* [def.encode(pkt).finish()];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { InvokeFn, Handler } from './handler';
|
|
2
|
+
export interface Mux {
|
|
3
|
+
register(handler: Handler): void;
|
|
4
|
+
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
5
|
+
}
|
|
6
|
+
export declare function createMux(): Mux;
|
|
7
|
+
export declare class StaticMux implements Mux {
|
|
8
|
+
private services;
|
|
9
|
+
register(handler: Handler): void;
|
|
10
|
+
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
11
|
+
}
|