starpc 0.50.0 → 0.51.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/integration/cross-language/ts-client.js +5 -79
- package/dist/integration/cross-language/ts-server.js +4 -100
- package/dist/srpc/client.d.ts +1 -3
- package/dist/srpc/client.js +2 -22
- package/dist/srpc/common-rpc.d.ts +1 -9
- package/dist/srpc/common-rpc.js +4 -62
- package/dist/srpc/handler.d.ts +1 -2
- package/dist/srpc/index.d.ts +0 -4
- package/dist/srpc/index.js +0 -3
- package/dist/srpc/invoker.d.ts +1 -2
- package/dist/srpc/rpcproto.pb.d.ts +0 -44
- package/dist/srpc/rpcproto.pb.js +0 -53
- package/dist/srpc/server-rpc.js +1 -3
- package/dist/srpc/server.test.js +35 -1
- package/echo/echo.go +8 -8
- package/echo/echo.pb.go +12 -36
- package/echo/echo_srpc.pb.cpp +1 -1
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/echo_srpc.pb.hpp +1 -1
- package/echo/echo_srpc.pb.rs +1 -1
- package/echo/server.go +1 -1
- package/go.mod +1 -1
- package/go.sum +2 -0
- package/integration/cross-language/go-client/main.go +4 -142
- package/integration/cross-language/go-server/main.go +2 -170
- package/integration/cross-language/run.bash +5 -105
- package/integration/cross-language/ts-client.ts +5 -90
- package/integration/cross-language/ts-server.ts +4 -112
- package/mock/mock.go +1 -1
- package/mock/mock.pb.go +12 -36
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +1 -1
- package/package.json +1 -1
- package/srpc/client-prefix.go +1 -1
- package/srpc/client-set.go +1 -1
- package/srpc/client.go +1 -1
- package/srpc/client.ts +4 -31
- package/srpc/common-rpc.go +15 -69
- package/srpc/common-rpc.ts +4 -77
- package/srpc/common-rpc_test.go +81 -0
- package/srpc/errors.go +26 -1
- package/srpc/handler.ts +1 -2
- package/srpc/index.ts +0 -4
- package/srpc/invoker-prefix.go +1 -1
- package/srpc/invoker.go +2 -2
- package/srpc/invoker.ts +5 -9
- package/srpc/message.go +1 -1
- package/srpc/msg-stream.go +1 -9
- package/srpc/mux-verbose.go +1 -1
- package/srpc/mux.go +1 -1
- package/srpc/rpcproto.pb.cc +4 -15
- package/srpc/rpcproto.pb.go +102 -419
- package/srpc/rpcproto.pb.h +0 -59
- package/srpc/rpcproto.pb.rs +0 -45
- package/srpc/rpcproto.pb.ts +0 -62
- package/srpc/rpcproto.proto +0 -16
- package/srpc/server-rpc.go +1 -1
- package/srpc/server-rpc.ts +1 -6
- package/srpc/server.test.ts +49 -1
- package/srpc/stream-pipe.go +1 -1
- package/srpc/stream-rwc.go +1 -1
- package/srpc/stream.go +0 -20
- package/dist/rpcstream/receipt.test.d.ts +0 -1
- package/dist/rpcstream/receipt.test.js +0 -41
- package/dist/srpc/call-receipt.d.ts +0 -17
- package/dist/srpc/call-receipt.js +0 -106
- package/dist/srpc/call-receipt.test.d.ts +0 -1
- package/dist/srpc/call-receipt.test.js +0 -375
- package/dist/srpc/server-invocation.d.ts +0 -17
- package/dist/srpc/server-invocation.js +0 -37
- package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
- package/srpc/call-receipt-e2e_test.go +0 -111
- package/srpc/call-receipt.go +0 -112
- package/srpc/call-receipt.test.ts +0 -441
- package/srpc/call-receipt.ts +0 -131
- package/srpc/call-receipt_test.go +0 -536
- package/srpc/schema-ownership_test.go +0 -115
- package/srpc/server-invocation.go +0 -40
- package/srpc/server-invocation.ts +0 -76
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
|
-
import { closeSync, openSync, writeSync } from 'node:fs';
|
|
3
2
|
import { pipe } from 'it-pipe';
|
|
4
3
|
import { pushable } from 'it-pushable';
|
|
5
4
|
import { Client } from '../../srpc/client.js';
|
|
6
5
|
import { parseLengthPrefixTransform, prependLengthPrefixTransform, } from '../../srpc/packet.js';
|
|
7
6
|
import { combineUint8ArrayListTransform } from '../../srpc/array-list.js';
|
|
8
|
-
import {
|
|
9
|
-
function emitReceiptEvent(line) {
|
|
10
|
-
console.log(line);
|
|
11
|
-
const fifo = process.env.RECEIPT_EVENT_FIFO;
|
|
12
|
-
if (!fifo) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const fd = openSync(fifo, 'w');
|
|
16
|
-
try {
|
|
17
|
-
writeSync(fd, `${line}\n`);
|
|
18
|
-
}
|
|
19
|
-
finally {
|
|
20
|
-
closeSync(fd);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
7
|
+
import { runClientTest } from '../../echo/index.js';
|
|
23
8
|
// tcpSocketToPacketStream wraps a Node.js TCP socket into a PacketStream.
|
|
24
9
|
function tcpSocketToPacketStream(socket) {
|
|
25
10
|
const socketSource = async function* () {
|
|
@@ -51,85 +36,26 @@ function tcpSocketToPacketStream(socket) {
|
|
|
51
36
|
};
|
|
52
37
|
}
|
|
53
38
|
async function main() {
|
|
54
|
-
const
|
|
55
|
-
const receiptCase = receiptMode ? process.argv[3] : undefined;
|
|
56
|
-
const addr = receiptMode ? process.argv[4] : process.argv[2];
|
|
39
|
+
const addr = process.argv[2];
|
|
57
40
|
if (!addr) {
|
|
58
|
-
console.error('usage: ts-client
|
|
41
|
+
console.error('usage: ts-client <host:port>');
|
|
59
42
|
process.exit(1);
|
|
60
43
|
}
|
|
61
|
-
if (receiptMode &&
|
|
62
|
-
!['commit', 'abort', 'loss', 'bare-close'].includes(receiptCase ?? '')) {
|
|
63
|
-
throw new Error(`unknown receipt case: ${receiptCase}`);
|
|
64
|
-
}
|
|
65
44
|
const [host, portStr] = addr.split(':');
|
|
66
45
|
const port = parseInt(portStr, 10);
|
|
67
|
-
let activeSocket;
|
|
68
46
|
const openStream = async () => {
|
|
69
47
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
70
48
|
const socket = net.connect(port, host, () => {
|
|
71
|
-
activeSocket = socket;
|
|
72
49
|
resolve(tcpSocketToPacketStream(socket));
|
|
73
50
|
});
|
|
74
51
|
socket.on('error', reject);
|
|
75
52
|
return promise;
|
|
76
53
|
};
|
|
77
54
|
const client = new Client(openStream);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const request = EchoMsg.create({ body: 'held receipt' });
|
|
81
|
-
const held = await client.requestWithReceipt('echo.Echoer', 'Echo', EchoMsg.toBinary(request));
|
|
82
|
-
const result = EchoMsg.fromBinary(held.response);
|
|
83
|
-
if (result.body !== request.body) {
|
|
84
|
-
throw new Error(`expected ${request.body}, got ${result.body}`);
|
|
85
|
-
}
|
|
86
|
-
switch (receiptCase) {
|
|
87
|
-
case 'commit':
|
|
88
|
-
await held.receipt.commit();
|
|
89
|
-
break;
|
|
90
|
-
case 'abort':
|
|
91
|
-
await held.receipt.abort();
|
|
92
|
-
break;
|
|
93
|
-
case 'loss':
|
|
94
|
-
activeSocket?.resetAndDestroy();
|
|
95
|
-
await expectReceiptFailure(held.receipt);
|
|
96
|
-
break;
|
|
97
|
-
case 'bare-close':
|
|
98
|
-
activeSocket?.end();
|
|
99
|
-
await expectReceiptFailure(held.receipt);
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
emitReceiptEvent(`CLIENT_RECEIPT_RESOLVED ${receiptTerminalName(receiptCase ?? '')}`);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
console.log('Running client test via TCP...');
|
|
106
|
-
await runClientTest(client);
|
|
107
|
-
}
|
|
55
|
+
console.log('Running client test via TCP...');
|
|
56
|
+
await runClientTest(client);
|
|
108
57
|
console.log('All tests passed.');
|
|
109
58
|
}
|
|
110
|
-
function receiptTerminalName(receiptCase) {
|
|
111
|
-
switch (receiptCase) {
|
|
112
|
-
case 'commit':
|
|
113
|
-
return 'committed';
|
|
114
|
-
case 'abort':
|
|
115
|
-
return 'canceled';
|
|
116
|
-
case 'loss':
|
|
117
|
-
return 'transportLost';
|
|
118
|
-
case 'bare-close':
|
|
119
|
-
return 'closed';
|
|
120
|
-
default:
|
|
121
|
-
return 'unknown';
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async function expectReceiptFailure(receipt) {
|
|
125
|
-
try {
|
|
126
|
-
await receipt.commit();
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
throw new Error('receipt commit unexpectedly succeeded');
|
|
132
|
-
}
|
|
133
59
|
process.on('unhandledRejection', (ev) => {
|
|
134
60
|
console.error('Unhandled rejection', ev);
|
|
135
61
|
process.exit(1);
|
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
|
-
import { closeSync, openSync, writeSync } from 'node:fs';
|
|
3
2
|
import { pipe } from 'it-pipe';
|
|
4
3
|
import { pushable } from 'it-pushable';
|
|
5
4
|
import { createMux, createHandler, Server } from '../../srpc/index.js';
|
|
6
5
|
import { parseLengthPrefixTransform, prependLengthPrefixTransform, } from '../../srpc/packet.js';
|
|
7
6
|
import { combineUint8ArrayListTransform } from '../../srpc/array-list.js';
|
|
8
|
-
import { EchoerServer
|
|
7
|
+
import { EchoerServer } from '../../echo/index.js';
|
|
9
8
|
import { EchoerDefinition } from '../../echo/echo_srpc.pb.js';
|
|
10
|
-
import { Packet, TerminalKind } from '../../srpc/rpcproto.pb.js';
|
|
11
|
-
function emitReceiptEvent(line) {
|
|
12
|
-
console.log(line);
|
|
13
|
-
const fifo = process.env.RECEIPT_EVENT_FIFO;
|
|
14
|
-
if (!fifo) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const fd = openSync(fifo, 'w');
|
|
18
|
-
try {
|
|
19
|
-
writeSync(fd, `${line}\n`);
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
closeSync(fd);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
9
|
// tcpSocketToPacketStream wraps a Node.js TCP socket into a PacketStream.
|
|
26
10
|
// Each Uint8Array in source/sink is one packet (no length prefix).
|
|
27
11
|
function tcpSocketToPacketStream(socket) {
|
|
@@ -41,15 +25,7 @@ function tcpSocketToPacketStream(socket) {
|
|
|
41
25
|
sink: async (source) => {
|
|
42
26
|
for await (const chunk of pipe(source, prependLengthPrefixTransform())) {
|
|
43
27
|
const data = chunk instanceof Uint8Array ? chunk : chunk.subarray();
|
|
44
|
-
|
|
45
|
-
const receiptCompletion = receiptMode &&
|
|
46
|
-
packet.body?.case === 'callData' &&
|
|
47
|
-
packet.body.value.complete &&
|
|
48
|
-
!packet.body.value.error;
|
|
49
|
-
if (receiptCompletion) {
|
|
50
|
-
emitReceiptEvent('SERVER_RECEIPT_ACK_WRITE committed');
|
|
51
|
-
}
|
|
52
|
-
const writeDone = new Promise((resolve, reject) => {
|
|
28
|
+
await new Promise((resolve, reject) => {
|
|
53
29
|
socket.write(data, (err) => {
|
|
54
30
|
if (err)
|
|
55
31
|
reject(err);
|
|
@@ -57,82 +33,15 @@ function tcpSocketToPacketStream(socket) {
|
|
|
57
33
|
resolve();
|
|
58
34
|
});
|
|
59
35
|
});
|
|
60
|
-
await writeDone;
|
|
61
|
-
if (receiptCompletion) {
|
|
62
|
-
finishReceiptServer();
|
|
63
|
-
}
|
|
64
36
|
}
|
|
65
37
|
socket.end();
|
|
66
38
|
},
|
|
67
39
|
};
|
|
68
40
|
}
|
|
69
41
|
const mux = createMux();
|
|
70
|
-
const receiptMode = process.argv[2] === 'receipt';
|
|
71
|
-
const receiptCase = receiptMode ? process.argv[3] : undefined;
|
|
72
|
-
if (receiptMode &&
|
|
73
|
-
!['commit', 'abort', 'loss', 'bare-close'].includes(receiptCase ?? '')) {
|
|
74
|
-
console.error(`unknown receipt case: ${receiptCase}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
const receiptServerDone = Promise.withResolvers();
|
|
78
|
-
let receiptServerFinished = false;
|
|
79
|
-
function finishReceiptServer() {
|
|
80
|
-
if (!receiptServerFinished) {
|
|
81
|
-
receiptServerFinished = true;
|
|
82
|
-
receiptServerDone.resolve();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (receiptMode) {
|
|
86
|
-
mux.registerLookupMethod(async (serviceID, methodID) => {
|
|
87
|
-
if (serviceID !== 'echo.Echoer' || methodID !== 'Echo') {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
return async (dataSource, dataSink, invocation) => {
|
|
91
|
-
let requestData;
|
|
92
|
-
for await (const data of dataSource) {
|
|
93
|
-
requestData = data;
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
if (!requestData) {
|
|
97
|
-
throw new Error('receipt request was empty');
|
|
98
|
-
}
|
|
99
|
-
const requestMsg = EchoMsg.fromBinary(requestData);
|
|
100
|
-
await dataSink((async function* () {
|
|
101
|
-
yield EchoMsg.toBinary(requestMsg);
|
|
102
|
-
if (!invocation) {
|
|
103
|
-
throw new Error('receipt invocation was missing');
|
|
104
|
-
}
|
|
105
|
-
emitReceiptEvent('SERVER_RECEIPT_WAITING');
|
|
106
|
-
const terminal = await invocation.waitTerminal(new AbortController().signal);
|
|
107
|
-
emitReceiptEvent(`SERVER_RECEIPT_TERMINAL ${terminalName(terminal)}`);
|
|
108
|
-
if (terminal !== TerminalKind.COMMITTED) {
|
|
109
|
-
finishReceiptServer();
|
|
110
|
-
}
|
|
111
|
-
})());
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function terminalName(terminal) {
|
|
116
|
-
switch (terminal) {
|
|
117
|
-
case TerminalKind.COMMITTED:
|
|
118
|
-
return 'committed';
|
|
119
|
-
case TerminalKind.CANCELED:
|
|
120
|
-
return 'canceled';
|
|
121
|
-
case TerminalKind.TRANSPORT_LOST:
|
|
122
|
-
return 'transportLost';
|
|
123
|
-
case TerminalKind.CLOSED:
|
|
124
|
-
return 'closed';
|
|
125
|
-
case TerminalKind.ABANDONED:
|
|
126
|
-
return 'abandoned';
|
|
127
|
-
default:
|
|
128
|
-
return 'unknown';
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
42
|
const server = new Server(mux.lookupMethod);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
mux.register(createHandler(EchoerDefinition, echoer));
|
|
135
|
-
}
|
|
43
|
+
const echoer = new EchoerServer(server);
|
|
44
|
+
mux.register(createHandler(EchoerDefinition, echoer));
|
|
136
45
|
const tcpServer = net.createServer((socket) => {
|
|
137
46
|
const stream = tcpSocketToPacketStream(socket);
|
|
138
47
|
server.handlePacketStream(stream);
|
|
@@ -141,11 +50,6 @@ tcpServer.listen(0, '127.0.0.1', () => {
|
|
|
141
50
|
const addr = tcpServer.address();
|
|
142
51
|
console.log(`LISTENING ${addr.address}:${addr.port}`);
|
|
143
52
|
});
|
|
144
|
-
if (receiptMode) {
|
|
145
|
-
void receiptServerDone.promise.then(() => {
|
|
146
|
-
tcpServer.close();
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
53
|
process.on('SIGINT', () => {
|
|
150
54
|
tcpServer.close();
|
|
151
55
|
process.exit(0);
|
package/dist/srpc/client.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { ProtoRpc } from './proto-rpc.js';
|
|
2
2
|
import type { OpenStreamFunc } from './stream.js';
|
|
3
|
-
|
|
4
|
-
export declare class Client implements ProtoRpc, ReceiptRpc {
|
|
3
|
+
export declare class Client implements ProtoRpc {
|
|
5
4
|
private openStreamCtr;
|
|
6
5
|
constructor(openStreamFn?: OpenStreamFunc);
|
|
7
6
|
setOpenStreamFn(openStreamFn?: OpenStreamFunc): void;
|
|
8
7
|
request(service: string, method: string, data: Uint8Array, abortSignal?: AbortSignal): Promise<Uint8Array>;
|
|
9
|
-
requestWithReceipt(service: string, method: string, data: Uint8Array, abortSignal?: AbortSignal): Promise<HeldCall>;
|
|
10
8
|
clientStreamingRequest(service: string, method: string, data: AsyncIterable<Uint8Array>, abortSignal?: AbortSignal): Promise<Uint8Array>;
|
|
11
9
|
serverStreamingRequest(service: string, method: string, data: Uint8Array, abortSignal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
12
10
|
bidirectionalStreamingRequest(service: string, method: string, data: AsyncIterable<Uint8Array>, abortSignal?: AbortSignal): AsyncIterable<Uint8Array>;
|
package/dist/srpc/client.js
CHANGED
|
@@ -5,7 +5,6 @@ import { ClientRPC } from './client-rpc.js';
|
|
|
5
5
|
import { writeToPushable } from './pushable.js';
|
|
6
6
|
import { decodePacketSource, encodePacketSource } from './packet.js';
|
|
7
7
|
import { OpenStreamCtr } from './open-stream-ctr.js';
|
|
8
|
-
import { CallReceipt } from './call-receipt.js';
|
|
9
8
|
// Client implements the ts-proto Rpc interface with the drpcproto protocol.
|
|
10
9
|
export class Client {
|
|
11
10
|
// openStreamCtr contains the OpenStreamFunc.
|
|
@@ -28,25 +27,6 @@ export class Client {
|
|
|
28
27
|
call.close(err);
|
|
29
28
|
throw err;
|
|
30
29
|
}
|
|
31
|
-
// requestWithReceipt reads one response and retains the call terminal.
|
|
32
|
-
async requestWithReceipt(service, method, data, abortSignal) {
|
|
33
|
-
const call = await this.startRpc(service, method, data, abortSignal);
|
|
34
|
-
const iterator = call.rpcDataSource[Symbol.asyncIterator]();
|
|
35
|
-
try {
|
|
36
|
-
const result = await iterator.next();
|
|
37
|
-
if (result.done) {
|
|
38
|
-
throw new Error('empty response');
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
response: result.value,
|
|
42
|
-
receipt: new CallReceipt(call, iterator),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
await call.close(err instanceof Error ? err : new Error('receipt read failed'));
|
|
47
|
-
throw err;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
30
|
// clientStreamingRequest starts a client side streaming request.
|
|
51
31
|
async clientStreamingRequest(service, method, data, abortSignal) {
|
|
52
32
|
const call = await this.startRpc(service, method, null, abortSignal);
|
|
@@ -102,8 +82,8 @@ export class Client {
|
|
|
102
82
|
const stream = await openStreamFn();
|
|
103
83
|
const call = new ClientRPC(rpcService, rpcMethod);
|
|
104
84
|
const onAbort = () => {
|
|
105
|
-
call.writeCallCancel();
|
|
106
|
-
call.close(new Error(ERR_RPC_ABORT));
|
|
85
|
+
void call.writeCallCancel().catch(() => undefined);
|
|
86
|
+
void call.close(new Error(ERR_RPC_ABORT)).catch(() => undefined);
|
|
107
87
|
};
|
|
108
88
|
abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
109
89
|
pipe(stream, decodePacketSource, call, encodePacketSource, stream)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Sink, Source } from 'it-stream-types';
|
|
2
|
-
import { Packet,
|
|
2
|
+
import { Packet, type CallData, type CallStart } from './rpcproto.pb.js';
|
|
3
3
|
export declare class CommonRPC {
|
|
4
4
|
readonly sink: Sink<Source<Packet>>;
|
|
5
5
|
readonly source: AsyncIterable<Packet>;
|
|
@@ -9,20 +9,12 @@ export declare class CommonRPC {
|
|
|
9
9
|
protected service?: string;
|
|
10
10
|
protected method?: string;
|
|
11
11
|
private closed?;
|
|
12
|
-
private remoteCompleted;
|
|
13
12
|
private remoteError?;
|
|
14
|
-
private remoteSourceClosed;
|
|
15
|
-
private remoteTerminal?;
|
|
16
13
|
private readonly invocationController;
|
|
17
|
-
private readonly terminalPromise;
|
|
18
|
-
private resolveTerminal;
|
|
19
14
|
private readonly writeDrainAbort;
|
|
20
15
|
constructor();
|
|
21
16
|
get isClosed(): boolean | Error;
|
|
22
17
|
protected get invocationSignal(): AbortSignal;
|
|
23
|
-
protected waitTerminal(ownerSignal: AbortSignal): Promise<TerminalKind>;
|
|
24
|
-
getTerminalKind(): TerminalKind | undefined;
|
|
25
|
-
private recordRemoteTerminal;
|
|
26
18
|
writeCallData(data?: Uint8Array, complete?: boolean, error?: string): Promise<void>;
|
|
27
19
|
private writeCallDataPacket;
|
|
28
20
|
writeCallCancel(waitForDrain?: boolean): Promise<void>;
|
package/dist/srpc/common-rpc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pushable } from 'it-pushable';
|
|
2
|
-
import { Packet
|
|
2
|
+
import { Packet } from './rpcproto.pb.js';
|
|
3
3
|
import { ERR_RPC_ABORT, RemoteRPCError } from './errors.js';
|
|
4
4
|
const maxBufferedOutgoingPackets = 1;
|
|
5
5
|
// CommonRPC is common logic between server and client RPCs.
|
|
@@ -24,25 +24,13 @@ export class CommonRPC {
|
|
|
24
24
|
method;
|
|
25
25
|
// closed indicates this rpc has been closed already.
|
|
26
26
|
closed;
|
|
27
|
-
// remoteCompleted is set only by an explicit remote CallData completion.
|
|
28
|
-
remoteCompleted = false;
|
|
29
27
|
// remoteError records a remote error or transport failure.
|
|
30
28
|
remoteError;
|
|
31
|
-
//
|
|
32
|
-
remoteSourceClosed = false;
|
|
33
|
-
// remoteTerminal is the first valid remote terminal.
|
|
34
|
-
remoteTerminal;
|
|
35
|
-
// invocationController cancels the server invocation on a remote terminal.
|
|
29
|
+
// invocationController cancels the server invocation when the RPC closes.
|
|
36
30
|
invocationController = new AbortController();
|
|
37
|
-
// terminalPromise resolves when a remote terminal is recorded.
|
|
38
|
-
terminalPromise;
|
|
39
|
-
resolveTerminal;
|
|
40
31
|
// writeDrainAbort wakes writers waiting for outbound stream drain on close.
|
|
41
32
|
writeDrainAbort = new AbortController();
|
|
42
33
|
constructor() {
|
|
43
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
44
|
-
this.terminalPromise = promise;
|
|
45
|
-
this.resolveTerminal = resolve;
|
|
46
34
|
this.sink = this._createSink();
|
|
47
35
|
this.source = this._source;
|
|
48
36
|
this.rpcDataSource = this._rpcDataSource;
|
|
@@ -51,49 +39,10 @@ export class CommonRPC {
|
|
|
51
39
|
get isClosed() {
|
|
52
40
|
return this.closed ?? false;
|
|
53
41
|
}
|
|
54
|
-
// invocationSignal is canceled when the RPC
|
|
42
|
+
// invocationSignal is canceled when the RPC closes.
|
|
55
43
|
get invocationSignal() {
|
|
56
44
|
return this.invocationController.signal;
|
|
57
45
|
}
|
|
58
|
-
// waitTerminal waits for the remote terminal or external owner cancellation.
|
|
59
|
-
async waitTerminal(ownerSignal) {
|
|
60
|
-
const { promise: ownerDone, resolve: resolveOwnerDone } = Promise.withResolvers();
|
|
61
|
-
const onAbort = () => resolveOwnerDone();
|
|
62
|
-
ownerSignal.addEventListener('abort', onAbort, { once: true });
|
|
63
|
-
let ownerAborted = ownerSignal.aborted;
|
|
64
|
-
try {
|
|
65
|
-
for (;;) {
|
|
66
|
-
const terminal = this.getTerminalKind();
|
|
67
|
-
if (terminal !== undefined) {
|
|
68
|
-
if (terminal === TerminalKind.CLOSED &&
|
|
69
|
-
this.remoteSourceClosed &&
|
|
70
|
-
!this.closed) {
|
|
71
|
-
await this.close();
|
|
72
|
-
}
|
|
73
|
-
return terminal;
|
|
74
|
-
}
|
|
75
|
-
if (ownerAborted) {
|
|
76
|
-
return TerminalKind.ABANDONED;
|
|
77
|
-
}
|
|
78
|
-
await Promise.race([this.terminalPromise, ownerDone]);
|
|
79
|
-
ownerAborted = ownerSignal.aborted;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
ownerSignal.removeEventListener('abort', onAbort);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// getTerminalKind returns the observed remote terminal, if any.
|
|
87
|
-
getTerminalKind() {
|
|
88
|
-
return this.remoteTerminal;
|
|
89
|
-
}
|
|
90
|
-
recordRemoteTerminal(kind) {
|
|
91
|
-
if (this.remoteTerminal !== undefined) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
this.remoteTerminal = kind;
|
|
95
|
-
this.resolveTerminal();
|
|
96
|
-
}
|
|
97
46
|
// writeCallData writes the call data packet.
|
|
98
47
|
async writeCallData(data, complete, error) {
|
|
99
48
|
await this.writeCallDataPacket(data, complete, error);
|
|
@@ -221,20 +170,16 @@ export class CommonRPC {
|
|
|
221
170
|
if (remoteError) {
|
|
222
171
|
this.remoteError ??= remoteError;
|
|
223
172
|
this.invocationController.abort();
|
|
224
|
-
this.recordRemoteTerminal(TerminalKind.TRANSPORT_LOST);
|
|
225
173
|
}
|
|
226
174
|
if (packet.complete && !remoteError) {
|
|
227
|
-
this.remoteCompleted = true;
|
|
228
|
-
this.recordRemoteTerminal(TerminalKind.COMMITTED);
|
|
229
175
|
this._rpcDataSource.end(remoteError);
|
|
230
176
|
}
|
|
231
177
|
else if (remoteError) {
|
|
232
178
|
this._rpcDataSource.end(remoteError);
|
|
233
179
|
}
|
|
234
180
|
}
|
|
235
|
-
// handleCallCancel
|
|
181
|
+
// handleCallCancel aborts the invocation and closes the call.
|
|
236
182
|
async handleCallCancel() {
|
|
237
|
-
this.recordRemoteTerminal(TerminalKind.CANCELED);
|
|
238
183
|
await this.close(new Error(ERR_RPC_ABORT));
|
|
239
184
|
}
|
|
240
185
|
// close closes the call, optionally with an error.
|
|
@@ -246,7 +191,6 @@ export class CommonRPC {
|
|
|
246
191
|
if (!this.remoteError && err) {
|
|
247
192
|
this.remoteError = err;
|
|
248
193
|
}
|
|
249
|
-
this.recordRemoteTerminal(err ? TerminalKind.TRANSPORT_LOST : TerminalKind.CLOSED);
|
|
250
194
|
this.invocationController.abort();
|
|
251
195
|
// note: this does nothing if _source is already ended.
|
|
252
196
|
if (err && err.message) {
|
|
@@ -272,8 +216,6 @@ export class CommonRPC {
|
|
|
272
216
|
await this.handlePacket(msg);
|
|
273
217
|
}
|
|
274
218
|
}
|
|
275
|
-
this.remoteSourceClosed = true;
|
|
276
|
-
this.recordRemoteTerminal(TerminalKind.CLOSED);
|
|
277
219
|
}
|
|
278
220
|
catch (err) {
|
|
279
221
|
this.close(err);
|
package/dist/srpc/handler.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Sink, Source } from 'it-stream-types';
|
|
2
2
|
import { ServiceDefinition, ServiceMethodDefinitions } from './definition.js';
|
|
3
|
-
|
|
4
|
-
export type InvokeFn = (dataSource: Source<Uint8Array>, dataSink: Sink<Source<Uint8Array>>, invocation?: ServerInvocation) => Promise<void>;
|
|
3
|
+
export type InvokeFn = (dataSource: Source<Uint8Array>, dataSink: Sink<Source<Uint8Array>>, invocation?: AbortSignal) => Promise<void>;
|
|
5
4
|
export interface Handler {
|
|
6
5
|
getServiceID(): string;
|
|
7
6
|
getMethodIDs(): string[];
|
package/dist/srpc/index.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export { ERR_RPC_ABORT, isAbortError, ERR_STREAM_IDLE, isStreamIdleError, castToError, } from './errors.js';
|
|
2
2
|
export { Client } from './client.js';
|
|
3
|
-
export { CallReceipt } from './call-receipt.js';
|
|
4
|
-
export type { HeldCall, ReceiptRpc } from './call-receipt.js';
|
|
5
|
-
export { ServerInvocation } from './server-invocation.js';
|
|
6
|
-
export { TerminalKind } from './rpcproto.pb.js';
|
|
7
3
|
export { Server } from './server.js';
|
|
8
4
|
export { StreamConn } from './conn.js';
|
|
9
5
|
export type { StreamConnParams, StreamHandler } from './conn.js';
|
package/dist/srpc/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export { ERR_RPC_ABORT, isAbortError, ERR_STREAM_IDLE, isStreamIdleError, castToError, } from './errors.js';
|
|
2
2
|
export { Client } from './client.js';
|
|
3
|
-
export { CallReceipt } from './call-receipt.js';
|
|
4
|
-
export { ServerInvocation } from './server-invocation.js';
|
|
5
|
-
export { TerminalKind } from './rpcproto.pb.js';
|
|
6
3
|
export { Server } from './server.js';
|
|
7
4
|
export { StreamConn } from './conn.js';
|
|
8
5
|
export { WebSocketConn } from './websocket.js';
|
package/dist/srpc/invoker.d.ts
CHANGED
|
@@ -2,6 +2,5 @@ import type { MethodDefinition } from './definition.js';
|
|
|
2
2
|
import { InvokeFn } from './handler.js';
|
|
3
3
|
import type { MessageType, Message } from '@aptre/protobuf-es-lite';
|
|
4
4
|
import { MethodIdempotency, MethodKind } from '@aptre/protobuf-es-lite';
|
|
5
|
-
|
|
6
|
-
export type MethodProto<R extends Message<R>, O extends Message<O>> = ((request: R, invocation?: ServerInvocation) => Promise<O>) | ((request: R, invocation?: ServerInvocation) => AsyncIterable<O>) | ((request: AsyncIterable<R>, invocation?: ServerInvocation) => Promise<O>) | ((request: AsyncIterable<R>, invocation?: ServerInvocation) => AsyncIterable<O>);
|
|
5
|
+
export type MethodProto<R extends Message<R>, O extends Message<O>> = ((request: R, invocation?: AbortSignal) => Promise<O>) | ((request: R, invocation?: AbortSignal) => AsyncIterable<O>) | ((request: AsyncIterable<R>, invocation?: AbortSignal) => Promise<O>) | ((request: AsyncIterable<R>, invocation?: AbortSignal) => AsyncIterable<O>);
|
|
7
6
|
export declare function createInvokeFn<R extends Message<R>, O extends Message<O>>(methodInfo: MethodDefinition<MessageType<R>, MessageType<O>, MethodKind, MethodIdempotency | undefined>, methodProto: MethodProto<R, O>): InvokeFn;
|
|
@@ -1,49 +1,5 @@
|
|
|
1
1
|
import type { MessageType } from '@aptre/protobuf-es-lite/message';
|
|
2
2
|
export declare const protobufPackage = "srpc";
|
|
3
|
-
/**
|
|
4
|
-
* TerminalKind is the terminal state of a held unary invocation.
|
|
5
|
-
*
|
|
6
|
-
* @generated from enum srpc.TerminalKind
|
|
7
|
-
*/
|
|
8
|
-
export declare enum TerminalKind {
|
|
9
|
-
/**
|
|
10
|
-
* TERMINAL_KIND_UNSPECIFIED is not a valid terminal state.
|
|
11
|
-
*
|
|
12
|
-
* @generated from enum value: TERMINAL_KIND_UNSPECIFIED = 0;
|
|
13
|
-
*/
|
|
14
|
-
UNSPECIFIED = 0,
|
|
15
|
-
/**
|
|
16
|
-
* TERMINAL_KIND_COMMITTED identifies an explicit remote CallData completion.
|
|
17
|
-
*
|
|
18
|
-
* @generated from enum value: TERMINAL_KIND_COMMITTED = 1;
|
|
19
|
-
*/
|
|
20
|
-
COMMITTED = 1,
|
|
21
|
-
/**
|
|
22
|
-
* TERMINAL_KIND_CANCELED identifies a remote CallCancel packet.
|
|
23
|
-
*
|
|
24
|
-
* @generated from enum value: TERMINAL_KIND_CANCELED = 2;
|
|
25
|
-
*/
|
|
26
|
-
CANCELED = 2,
|
|
27
|
-
/**
|
|
28
|
-
* TERMINAL_KIND_TRANSPORT_LOST identifies a remote error or transport failure.
|
|
29
|
-
*
|
|
30
|
-
* @generated from enum value: TERMINAL_KIND_TRANSPORT_LOST = 3;
|
|
31
|
-
*/
|
|
32
|
-
TRANSPORT_LOST = 3,
|
|
33
|
-
/**
|
|
34
|
-
* TERMINAL_KIND_CLOSED identifies a bare remote close without completion.
|
|
35
|
-
*
|
|
36
|
-
* @generated from enum value: TERMINAL_KIND_CLOSED = 4;
|
|
37
|
-
*/
|
|
38
|
-
CLOSED = 4,
|
|
39
|
-
/**
|
|
40
|
-
* TERMINAL_KIND_ABANDONED identifies owner-context expiry without a remote terminal.
|
|
41
|
-
*
|
|
42
|
-
* @generated from enum value: TERMINAL_KIND_ABANDONED = 5;
|
|
43
|
-
*/
|
|
44
|
-
ABANDONED = 5
|
|
45
|
-
}
|
|
46
|
-
export declare const TerminalKind_Enum: import("@aptre/protobuf-es-lite/enum").EnumType;
|
|
47
3
|
/**
|
|
48
4
|
* CallStart requests starting a new RPC call.
|
|
49
5
|
*
|
package/dist/srpc/rpcproto.pb.js
CHANGED
|
@@ -1,62 +1,9 @@
|
|
|
1
1
|
// @generated by protoc-gen-es-lite unknown with parameter "target=ts,ts_nocheck=false"
|
|
2
2
|
// @generated from file github.com/aperturerobotics/starpc/srpc/rpcproto.proto (package srpc, syntax proto3)
|
|
3
3
|
/* eslint-disable */
|
|
4
|
-
import { createEnumType } from '@aptre/protobuf-es-lite/enum';
|
|
5
4
|
import { createMessageType } from '@aptre/protobuf-es-lite/message';
|
|
6
5
|
import { ScalarType } from '@aptre/protobuf-es-lite/scalar';
|
|
7
6
|
export const protobufPackage = 'srpc';
|
|
8
|
-
/**
|
|
9
|
-
* TerminalKind is the terminal state of a held unary invocation.
|
|
10
|
-
*
|
|
11
|
-
* @generated from enum srpc.TerminalKind
|
|
12
|
-
*/
|
|
13
|
-
export var TerminalKind;
|
|
14
|
-
(function (TerminalKind) {
|
|
15
|
-
/**
|
|
16
|
-
* TERMINAL_KIND_UNSPECIFIED is not a valid terminal state.
|
|
17
|
-
*
|
|
18
|
-
* @generated from enum value: TERMINAL_KIND_UNSPECIFIED = 0;
|
|
19
|
-
*/
|
|
20
|
-
TerminalKind[TerminalKind["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
21
|
-
/**
|
|
22
|
-
* TERMINAL_KIND_COMMITTED identifies an explicit remote CallData completion.
|
|
23
|
-
*
|
|
24
|
-
* @generated from enum value: TERMINAL_KIND_COMMITTED = 1;
|
|
25
|
-
*/
|
|
26
|
-
TerminalKind[TerminalKind["COMMITTED"] = 1] = "COMMITTED";
|
|
27
|
-
/**
|
|
28
|
-
* TERMINAL_KIND_CANCELED identifies a remote CallCancel packet.
|
|
29
|
-
*
|
|
30
|
-
* @generated from enum value: TERMINAL_KIND_CANCELED = 2;
|
|
31
|
-
*/
|
|
32
|
-
TerminalKind[TerminalKind["CANCELED"] = 2] = "CANCELED";
|
|
33
|
-
/**
|
|
34
|
-
* TERMINAL_KIND_TRANSPORT_LOST identifies a remote error or transport failure.
|
|
35
|
-
*
|
|
36
|
-
* @generated from enum value: TERMINAL_KIND_TRANSPORT_LOST = 3;
|
|
37
|
-
*/
|
|
38
|
-
TerminalKind[TerminalKind["TRANSPORT_LOST"] = 3] = "TRANSPORT_LOST";
|
|
39
|
-
/**
|
|
40
|
-
* TERMINAL_KIND_CLOSED identifies a bare remote close without completion.
|
|
41
|
-
*
|
|
42
|
-
* @generated from enum value: TERMINAL_KIND_CLOSED = 4;
|
|
43
|
-
*/
|
|
44
|
-
TerminalKind[TerminalKind["CLOSED"] = 4] = "CLOSED";
|
|
45
|
-
/**
|
|
46
|
-
* TERMINAL_KIND_ABANDONED identifies owner-context expiry without a remote terminal.
|
|
47
|
-
*
|
|
48
|
-
* @generated from enum value: TERMINAL_KIND_ABANDONED = 5;
|
|
49
|
-
*/
|
|
50
|
-
TerminalKind[TerminalKind["ABANDONED"] = 5] = "ABANDONED";
|
|
51
|
-
})(TerminalKind || (TerminalKind = {}));
|
|
52
|
-
export const TerminalKind_Enum = /* @__PURE__ */ createEnumType('srpc.TerminalKind', [
|
|
53
|
-
[0, 'TERMINAL_KIND_UNSPECIFIED'],
|
|
54
|
-
[1, 'TERMINAL_KIND_COMMITTED'],
|
|
55
|
-
[2, 'TERMINAL_KIND_CANCELED'],
|
|
56
|
-
[3, 'TERMINAL_KIND_TRANSPORT_LOST'],
|
|
57
|
-
[4, 'TERMINAL_KIND_CLOSED'],
|
|
58
|
-
[5, 'TERMINAL_KIND_ABANDONED'],
|
|
59
|
-
]);
|
|
60
7
|
export const CallStart =
|
|
61
8
|
/* @__PURE__ */ createMessageType({
|
|
62
9
|
typeName: 'srpc.CallStart',
|
package/dist/srpc/server-rpc.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CommonRPC } from './common-rpc.js';
|
|
2
|
-
import { ServerInvocation } from './server-invocation.js';
|
|
3
2
|
// ServerRPC is an ongoing RPC from the server side.
|
|
4
3
|
export class ServerRPC extends CommonRPC {
|
|
5
4
|
// lookupMethod looks up the incoming RPC methods.
|
|
@@ -38,9 +37,8 @@ export class ServerRPC extends CommonRPC {
|
|
|
38
37
|
// invokeRPC starts invoking the RPC handler.
|
|
39
38
|
async invokeRPC(invokeFn) {
|
|
40
39
|
const dataSink = this._createDataSink();
|
|
41
|
-
const invocation = new ServerInvocation(this.invocationSignal, (ownerSignal) => this.waitTerminal(ownerSignal));
|
|
42
40
|
try {
|
|
43
|
-
await invokeFn(this.rpcDataSource, dataSink,
|
|
41
|
+
await invokeFn(this.rpcDataSource, dataSink, this.invocationSignal);
|
|
44
42
|
}
|
|
45
43
|
catch (err) {
|
|
46
44
|
this.close(err);
|