starpc 0.50.0 → 0.52.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/cmd/protoc-gen-es-starpc/typescript.ts +37 -0
- package/dist/cmd/protoc-gen-es-starpc/typescript.js +24 -0
- package/dist/echo/echo_srpc.pb.d.ts +44 -1
- package/dist/echo/server.d.ts +9 -8
- package/dist/echo/server.js +6 -6
- package/dist/integration/cross-language/ts-client.js +5 -79
- package/dist/integration/cross-language/ts-server.js +4 -100
- package/dist/mock/mock_srpc.pb.d.ts +14 -1
- package/dist/srpc/channel.js +6 -3
- package/dist/srpc/channel.test.js +20 -1
- 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 +12 -4
- package/dist/srpc/index.d.ts +2 -4
- package/dist/srpc/index.js +1 -3
- package/dist/srpc/invoker.d.ts +2 -2
- package/dist/srpc/invoker.js +2 -2
- package/dist/srpc/rpcproto.pb.d.ts +0 -44
- package/dist/srpc/rpcproto.pb.js +0 -53
- package/dist/srpc/server-context.d.ts +11 -0
- package/dist/srpc/server-context.js +28 -0
- package/dist/srpc/server-rpc.js +3 -3
- package/dist/srpc/server.test.js +65 -2
- package/dist/srpc/watchdog.test.js +1 -0
- 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/echo_srpc.pb.ts +74 -0
- package/echo/server.go +1 -1
- package/echo/server.ts +24 -5
- 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/mock/mock_srpc.pb.ts +19 -1
- package/package.json +2 -2
- package/srpc/channel.test.ts +21 -1
- package/srpc/channel.ts +7 -3
- 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 +54 -5
- package/srpc/index.ts +7 -4
- package/srpc/invoker-prefix.go +1 -1
- package/srpc/invoker.go +2 -2
- package/srpc/invoker.ts +20 -7
- 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-context.ts +55 -0
- package/srpc/server-rpc.go +1 -1
- package/srpc/server-rpc.ts +4 -6
- package/srpc/server.test.ts +94 -1
- package/srpc/stream-pipe.go +1 -1
- package/srpc/stream-rwc.go +1 -1
- package/srpc/stream.go +0 -20
- package/srpc/watchdog.test.ts +1 -0
- 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
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from '@aptre/protobuf-es-lite/protoplugin/ecmascript'
|
|
27
27
|
|
|
28
28
|
const MessageStream = createImportSymbol('MessageStream', 'starpc')
|
|
29
|
+
const ServerContext = createImportSymbol('ServerContext', 'starpc')
|
|
29
30
|
const RuntimeMethodKind = createImportSymbol(
|
|
30
31
|
'MethodKind',
|
|
31
32
|
'@aptre/protobuf-es-lite',
|
|
@@ -128,6 +129,42 @@ function generateService(
|
|
|
128
129
|
f.print("}");
|
|
129
130
|
f.print();
|
|
130
131
|
|
|
132
|
+
// Generate the server implementation interface.
|
|
133
|
+
f.print(f.jsDoc(service));
|
|
134
|
+
f.print("export interface ", localName(service), "Handler {");
|
|
135
|
+
for (let i = 0; i < service.methods.length; i++) {
|
|
136
|
+
const method = service.methods[i];
|
|
137
|
+
f.print(f.jsDoc(method, " "));
|
|
138
|
+
if (method.methodKind === MethodKind.Unary) {
|
|
139
|
+
f.print(
|
|
140
|
+
" ", method.name,
|
|
141
|
+
"(request: ", method.input, ", abortSignal: AbortSignal, context: ", ServerContext, "): ",
|
|
142
|
+
"Promise<", method.output, ">;"
|
|
143
|
+
);
|
|
144
|
+
} else if (method.methodKind === MethodKind.ServerStreaming) {
|
|
145
|
+
f.print(
|
|
146
|
+
" ", method.name,
|
|
147
|
+
"(request: ", method.input, ", abortSignal: AbortSignal, context: ", ServerContext, "): ",
|
|
148
|
+
MessageStream, "<", method.output, ">;"
|
|
149
|
+
);
|
|
150
|
+
} else if (method.methodKind === MethodKind.ClientStreaming) {
|
|
151
|
+
f.print(
|
|
152
|
+
" ", method.name,
|
|
153
|
+
"(request: ", MessageStream, "<", method.input, ">, abortSignal: AbortSignal, context: ", ServerContext, "): ",
|
|
154
|
+
"Promise<", method.output, ">;"
|
|
155
|
+
);
|
|
156
|
+
} else if (method.methodKind === MethodKind.BiDiStreaming) {
|
|
157
|
+
f.print(
|
|
158
|
+
" ", method.name,
|
|
159
|
+
"(request: ", MessageStream, "<", method.input, ">, abortSignal: AbortSignal, context: ", ServerContext, "): ",
|
|
160
|
+
MessageStream, "<", method.output, ">;"
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (i < service.methods.length - 1) f.print();
|
|
164
|
+
}
|
|
165
|
+
f.print("}");
|
|
166
|
+
f.print();
|
|
167
|
+
|
|
131
168
|
|
|
132
169
|
// Generate the service name constant
|
|
133
170
|
f.print("export const ", localName(service), "ServiceName = ", localName(service), "Definition.typeName");
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { MethodIdempotency, MethodKind, localName, } from '@aptre/protobuf-es-lite';
|
|
16
16
|
import { createImportSymbol, } from '@aptre/protobuf-es-lite/protoplugin/ecmascript';
|
|
17
17
|
const MessageStream = createImportSymbol('MessageStream', 'starpc');
|
|
18
|
+
const ServerContext = createImportSymbol('ServerContext', 'starpc');
|
|
18
19
|
const RuntimeMethodKind = createImportSymbol('MethodKind', '@aptre/protobuf-es-lite');
|
|
19
20
|
const RuntimeMethodIdempotency = createImportSymbol('MethodIdempotency', '@aptre/protobuf-es-lite');
|
|
20
21
|
// const Message = createImportSymbol('Message', '@aptre/protobuf-es-lite')
|
|
@@ -74,6 +75,29 @@ function generateService(f, service) {
|
|
|
74
75
|
}
|
|
75
76
|
f.print("}");
|
|
76
77
|
f.print();
|
|
78
|
+
// Generate the server implementation interface.
|
|
79
|
+
f.print(f.jsDoc(service));
|
|
80
|
+
f.print("export interface ", localName(service), "Handler {");
|
|
81
|
+
for (let i = 0; i < service.methods.length; i++) {
|
|
82
|
+
const method = service.methods[i];
|
|
83
|
+
f.print(f.jsDoc(method, " "));
|
|
84
|
+
if (method.methodKind === MethodKind.Unary) {
|
|
85
|
+
f.print(" ", method.name, "(request: ", method.input, ", abortSignal: AbortSignal, context: ", ServerContext, "): ", "Promise<", method.output, ">;");
|
|
86
|
+
}
|
|
87
|
+
else if (method.methodKind === MethodKind.ServerStreaming) {
|
|
88
|
+
f.print(" ", method.name, "(request: ", method.input, ", abortSignal: AbortSignal, context: ", ServerContext, "): ", MessageStream, "<", method.output, ">;");
|
|
89
|
+
}
|
|
90
|
+
else if (method.methodKind === MethodKind.ClientStreaming) {
|
|
91
|
+
f.print(" ", method.name, "(request: ", MessageStream, "<", method.input, ">, abortSignal: AbortSignal, context: ", ServerContext, "): ", "Promise<", method.output, ">;");
|
|
92
|
+
}
|
|
93
|
+
else if (method.methodKind === MethodKind.BiDiStreaming) {
|
|
94
|
+
f.print(" ", method.name, "(request: ", MessageStream, "<", method.input, ">, abortSignal: AbortSignal, context: ", ServerContext, "): ", MessageStream, "<", method.output, ">;");
|
|
95
|
+
}
|
|
96
|
+
if (i < service.methods.length - 1)
|
|
97
|
+
f.print();
|
|
98
|
+
}
|
|
99
|
+
f.print("}");
|
|
100
|
+
f.print();
|
|
77
101
|
// Generate the service name constant
|
|
78
102
|
f.print("export const ", localName(service), "ServiceName = ", localName(service), "Definition.typeName");
|
|
79
103
|
f.print();
|
|
@@ -2,7 +2,7 @@ import { EchoMsg } from './echo.pb.js';
|
|
|
2
2
|
import { MethodKind } from '@aptre/protobuf-es-lite';
|
|
3
3
|
import { RpcStreamPacket } from '../rpcstream/rpcstream.pb.js';
|
|
4
4
|
import { Empty } from '@aptre/protobuf-es-lite/google/protobuf/empty';
|
|
5
|
-
import { MessageStream, ProtoRpc } from 'starpc';
|
|
5
|
+
import { MessageStream, ProtoRpc, ServerContext } from 'starpc';
|
|
6
6
|
/**
|
|
7
7
|
* Echoer service returns the given message.
|
|
8
8
|
*
|
|
@@ -122,6 +122,49 @@ export interface Echoer {
|
|
|
122
122
|
*/
|
|
123
123
|
DoNothing(request: Empty, abortSignal?: AbortSignal): Promise<Empty>;
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Echoer service returns the given message.
|
|
127
|
+
*
|
|
128
|
+
* @generated from service echo.Echoer
|
|
129
|
+
*/
|
|
130
|
+
export interface EchoerHandler {
|
|
131
|
+
/**
|
|
132
|
+
* Echo returns the given message.
|
|
133
|
+
*
|
|
134
|
+
* @generated from rpc echo.Echoer.Echo
|
|
135
|
+
*/
|
|
136
|
+
Echo(request: EchoMsg, abortSignal: AbortSignal, context: ServerContext): Promise<EchoMsg>;
|
|
137
|
+
/**
|
|
138
|
+
* EchoServerStream is an example of a server -> client one-way stream.
|
|
139
|
+
*
|
|
140
|
+
* @generated from rpc echo.Echoer.EchoServerStream
|
|
141
|
+
*/
|
|
142
|
+
EchoServerStream(request: EchoMsg, abortSignal: AbortSignal, context: ServerContext): MessageStream<EchoMsg>;
|
|
143
|
+
/**
|
|
144
|
+
* EchoClientStream is an example of client->server one-way stream.
|
|
145
|
+
*
|
|
146
|
+
* @generated from rpc echo.Echoer.EchoClientStream
|
|
147
|
+
*/
|
|
148
|
+
EchoClientStream(request: MessageStream<EchoMsg>, abortSignal: AbortSignal, context: ServerContext): Promise<EchoMsg>;
|
|
149
|
+
/**
|
|
150
|
+
* EchoBidiStream is an example of a two-way stream.
|
|
151
|
+
*
|
|
152
|
+
* @generated from rpc echo.Echoer.EchoBidiStream
|
|
153
|
+
*/
|
|
154
|
+
EchoBidiStream(request: MessageStream<EchoMsg>, abortSignal: AbortSignal, context: ServerContext): MessageStream<EchoMsg>;
|
|
155
|
+
/**
|
|
156
|
+
* RpcStream opens a nested rpc call stream.
|
|
157
|
+
*
|
|
158
|
+
* @generated from rpc echo.Echoer.RpcStream
|
|
159
|
+
*/
|
|
160
|
+
RpcStream(request: MessageStream<RpcStreamPacket>, abortSignal: AbortSignal, context: ServerContext): MessageStream<RpcStreamPacket>;
|
|
161
|
+
/**
|
|
162
|
+
* DoNothing does nothing.
|
|
163
|
+
*
|
|
164
|
+
* @generated from rpc echo.Echoer.DoNothing
|
|
165
|
+
*/
|
|
166
|
+
DoNothing(request: Empty, abortSignal: AbortSignal, context: ServerContext): Promise<Empty>;
|
|
167
|
+
}
|
|
125
168
|
export declare const EchoerServiceName: "echo.Echoer";
|
|
126
169
|
export declare class EchoerClient implements Echoer {
|
|
127
170
|
private readonly rpc;
|
package/dist/echo/server.d.ts
CHANGED
|
@@ -2,16 +2,17 @@ import { Message } from '@aptre/protobuf-es-lite';
|
|
|
2
2
|
import { Empty } from '@aptre/protobuf-es-lite/google/protobuf/empty';
|
|
3
3
|
import { EchoMsg } from './echo.pb.js';
|
|
4
4
|
import { Server } from '../srpc/server.js';
|
|
5
|
+
import type { ServerContext } from '../srpc/server-context.js';
|
|
5
6
|
import { RpcStreamPacket } from '../rpcstream/rpcstream.pb.js';
|
|
6
7
|
import { MessageStream } from '../srpc/message.js';
|
|
7
|
-
import {
|
|
8
|
-
export declare class EchoerServer implements
|
|
8
|
+
import type { EchoerHandler } from './echo_srpc.pb.js';
|
|
9
|
+
export declare class EchoerServer implements EchoerHandler {
|
|
9
10
|
private proxyServer?;
|
|
10
11
|
constructor(proxyServer?: Server);
|
|
11
|
-
Echo(request: EchoMsg): Promise<Message<EchoMsg>>;
|
|
12
|
-
EchoServerStream(request: EchoMsg): MessageStream<EchoMsg>;
|
|
13
|
-
EchoClientStream(request: MessageStream<EchoMsg
|
|
14
|
-
EchoBidiStream(request: MessageStream<EchoMsg
|
|
15
|
-
RpcStream(request: MessageStream<RpcStreamPacket
|
|
16
|
-
DoNothing(): Promise<Empty>;
|
|
12
|
+
Echo(request: EchoMsg, _abortSignal: AbortSignal, _context: ServerContext): Promise<Message<EchoMsg>>;
|
|
13
|
+
EchoServerStream(request: EchoMsg, _abortSignal: AbortSignal, _context: ServerContext): MessageStream<EchoMsg>;
|
|
14
|
+
EchoClientStream(request: MessageStream<EchoMsg>, _abortSignal: AbortSignal, _context: ServerContext): Promise<Message<EchoMsg>>;
|
|
15
|
+
EchoBidiStream(request: MessageStream<EchoMsg>, _abortSignal: AbortSignal, _context: ServerContext): MessageStream<EchoMsg>;
|
|
16
|
+
RpcStream(request: MessageStream<RpcStreamPacket>, _abortSignal: AbortSignal, _context: ServerContext): MessageStream<RpcStreamPacket>;
|
|
17
|
+
DoNothing(_request: Empty, _abortSignal: AbortSignal, _context: ServerContext): Promise<Empty>;
|
|
17
18
|
}
|
package/dist/echo/server.js
CHANGED
|
@@ -8,16 +8,16 @@ export class EchoerServer {
|
|
|
8
8
|
constructor(proxyServer) {
|
|
9
9
|
this.proxyServer = proxyServer;
|
|
10
10
|
}
|
|
11
|
-
async Echo(request) {
|
|
11
|
+
async Echo(request, _abortSignal, _context) {
|
|
12
12
|
return request;
|
|
13
13
|
}
|
|
14
|
-
async *EchoServerStream(request) {
|
|
14
|
+
async *EchoServerStream(request, _abortSignal, _context) {
|
|
15
15
|
for (let i = 0; i < 5; i++) {
|
|
16
16
|
yield request;
|
|
17
17
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
async EchoClientStream(request) {
|
|
20
|
+
async EchoClientStream(request, _abortSignal, _context) {
|
|
21
21
|
// return the first message sent by the client.
|
|
22
22
|
const message = await first(request);
|
|
23
23
|
if (!message) {
|
|
@@ -25,14 +25,14 @@ export class EchoerServer {
|
|
|
25
25
|
}
|
|
26
26
|
return message;
|
|
27
27
|
}
|
|
28
|
-
EchoBidiStream(request) {
|
|
28
|
+
EchoBidiStream(request, _abortSignal, _context) {
|
|
29
29
|
// build result observable
|
|
30
30
|
const result = messagePushable();
|
|
31
31
|
result.push({ body: 'hello from server' });
|
|
32
32
|
writeToPushable(request, result);
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
|
-
RpcStream(request) {
|
|
35
|
+
RpcStream(request, _abortSignal, _context) {
|
|
36
36
|
return handleRpcStream(request[Symbol.asyncIterator](), async () => {
|
|
37
37
|
if (!this.proxyServer) {
|
|
38
38
|
throw new Error('rpc stream proxy server not set');
|
|
@@ -40,7 +40,7 @@ export class EchoerServer {
|
|
|
40
40
|
return this.proxyServer.rpcStreamHandler;
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
-
async DoNothing() {
|
|
43
|
+
async DoNothing(_request, _abortSignal, _context) {
|
|
44
44
|
return {};
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MockMsg } from './mock.pb.js';
|
|
2
2
|
import { MethodKind } from '@aptre/protobuf-es-lite';
|
|
3
|
-
import { ProtoRpc } from 'starpc';
|
|
3
|
+
import { ProtoRpc, ServerContext } from 'starpc';
|
|
4
4
|
/**
|
|
5
5
|
* Mock service mocks some RPCs for the e2e tests.
|
|
6
6
|
*
|
|
@@ -35,6 +35,19 @@ export interface Mock {
|
|
|
35
35
|
*/
|
|
36
36
|
MockRequest(request: MockMsg, abortSignal?: AbortSignal): Promise<MockMsg>;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Mock service mocks some RPCs for the e2e tests.
|
|
40
|
+
*
|
|
41
|
+
* @generated from service e2e.mock.Mock
|
|
42
|
+
*/
|
|
43
|
+
export interface MockHandler {
|
|
44
|
+
/**
|
|
45
|
+
* MockRequest runs a mock unary request.
|
|
46
|
+
*
|
|
47
|
+
* @generated from rpc e2e.mock.Mock.MockRequest
|
|
48
|
+
*/
|
|
49
|
+
MockRequest(request: MockMsg, abortSignal: AbortSignal, context: ServerContext): Promise<MockMsg>;
|
|
50
|
+
}
|
|
38
51
|
export declare const MockServiceName: "e2e.mock.Mock";
|
|
39
52
|
export declare class MockClient implements Mock {
|
|
40
53
|
private readonly rpc;
|
package/dist/srpc/channel.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { pushable } from 'it-pushable';
|
|
2
2
|
import { Watchdog } from './watchdog.js';
|
|
3
3
|
import { ERR_STREAM_IDLE } from './errors.js';
|
|
4
|
+
function isMessagePort(channel) {
|
|
5
|
+
return 'postMessage' in channel && 'start' in channel;
|
|
6
|
+
}
|
|
4
7
|
// ChannelStream implements a Stream over a BroadcastChannel duplex or MessagePort.
|
|
5
8
|
//
|
|
6
9
|
// NOTE: there is no way to tell if a BroadcastChannel or MessagePort is closed.
|
|
@@ -99,7 +102,7 @@ export class ChannelStream {
|
|
|
99
102
|
this._source = source;
|
|
100
103
|
// wire up the message handlers
|
|
101
104
|
const onMessage = this.onMessage.bind(this);
|
|
102
|
-
if (channel
|
|
105
|
+
if (isMessagePort(channel)) {
|
|
103
106
|
// MessagePort
|
|
104
107
|
channel.onmessage = onMessage;
|
|
105
108
|
channel.start();
|
|
@@ -124,7 +127,7 @@ export class ChannelStream {
|
|
|
124
127
|
return;
|
|
125
128
|
}
|
|
126
129
|
msg.from = this.localId;
|
|
127
|
-
if (this.channel
|
|
130
|
+
if (isMessagePort(this.channel)) {
|
|
128
131
|
this.channel.postMessage(msg);
|
|
129
132
|
}
|
|
130
133
|
else {
|
|
@@ -165,7 +168,7 @@ export class ChannelStream {
|
|
|
165
168
|
this.localWriteClosed = true;
|
|
166
169
|
this.remoteWriteClosed = true;
|
|
167
170
|
// close channels
|
|
168
|
-
if (this.channel
|
|
171
|
+
if (isMessagePort(this.channel)) {
|
|
169
172
|
this.channel.onmessage = null;
|
|
170
173
|
this.channel.close();
|
|
171
174
|
}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { pushable } from 'it-pushable';
|
|
3
3
|
import { ChannelStream } from './channel.js';
|
|
4
4
|
describe('ChannelStream', () => {
|
|
5
|
+
it('recognizes MessagePort implementations from another realm', () => {
|
|
6
|
+
const port = {
|
|
7
|
+
close: vi.fn(),
|
|
8
|
+
onmessage: null,
|
|
9
|
+
postMessage: vi.fn(),
|
|
10
|
+
start: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
const stream = new ChannelStream('client', port);
|
|
13
|
+
try {
|
|
14
|
+
expect(port.start).toHaveBeenCalledOnce();
|
|
15
|
+
expect(port.postMessage).toHaveBeenCalledWith({
|
|
16
|
+
ack: true,
|
|
17
|
+
from: 'client',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
stream.close();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
5
24
|
it('keeps MessagePort peer writes open after local source completes normally', async () => {
|
|
6
25
|
const { port1, port2 } = new MessageChannel();
|
|
7
26
|
const client = new ChannelStream('client', port1);
|
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>;
|