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.
Files changed (100) hide show
  1. package/cmd/protoc-gen-es-starpc/typescript.ts +37 -0
  2. package/dist/cmd/protoc-gen-es-starpc/typescript.js +24 -0
  3. package/dist/echo/echo_srpc.pb.d.ts +44 -1
  4. package/dist/echo/server.d.ts +9 -8
  5. package/dist/echo/server.js +6 -6
  6. package/dist/integration/cross-language/ts-client.js +5 -79
  7. package/dist/integration/cross-language/ts-server.js +4 -100
  8. package/dist/mock/mock_srpc.pb.d.ts +14 -1
  9. package/dist/srpc/channel.js +6 -3
  10. package/dist/srpc/channel.test.js +20 -1
  11. package/dist/srpc/client.d.ts +1 -3
  12. package/dist/srpc/client.js +2 -22
  13. package/dist/srpc/common-rpc.d.ts +1 -9
  14. package/dist/srpc/common-rpc.js +4 -62
  15. package/dist/srpc/handler.d.ts +12 -4
  16. package/dist/srpc/index.d.ts +2 -4
  17. package/dist/srpc/index.js +1 -3
  18. package/dist/srpc/invoker.d.ts +2 -2
  19. package/dist/srpc/invoker.js +2 -2
  20. package/dist/srpc/rpcproto.pb.d.ts +0 -44
  21. package/dist/srpc/rpcproto.pb.js +0 -53
  22. package/dist/srpc/server-context.d.ts +11 -0
  23. package/dist/srpc/server-context.js +28 -0
  24. package/dist/srpc/server-rpc.js +3 -3
  25. package/dist/srpc/server.test.js +65 -2
  26. package/dist/srpc/watchdog.test.js +1 -0
  27. package/echo/echo.go +8 -8
  28. package/echo/echo.pb.go +12 -36
  29. package/echo/echo_srpc.pb.cpp +1 -1
  30. package/echo/echo_srpc.pb.go +1 -1
  31. package/echo/echo_srpc.pb.hpp +1 -1
  32. package/echo/echo_srpc.pb.rs +1 -1
  33. package/echo/echo_srpc.pb.ts +74 -0
  34. package/echo/server.go +1 -1
  35. package/echo/server.ts +24 -5
  36. package/go.mod +1 -1
  37. package/go.sum +2 -0
  38. package/integration/cross-language/go-client/main.go +4 -142
  39. package/integration/cross-language/go-server/main.go +2 -170
  40. package/integration/cross-language/run.bash +5 -105
  41. package/integration/cross-language/ts-client.ts +5 -90
  42. package/integration/cross-language/ts-server.ts +4 -112
  43. package/mock/mock.go +1 -1
  44. package/mock/mock.pb.go +12 -36
  45. package/mock/mock_srpc.pb.cpp +1 -1
  46. package/mock/mock_srpc.pb.go +1 -1
  47. package/mock/mock_srpc.pb.hpp +1 -1
  48. package/mock/mock_srpc.pb.rs +1 -1
  49. package/mock/mock_srpc.pb.ts +19 -1
  50. package/package.json +2 -2
  51. package/srpc/channel.test.ts +21 -1
  52. package/srpc/channel.ts +7 -3
  53. package/srpc/client-prefix.go +1 -1
  54. package/srpc/client-set.go +1 -1
  55. package/srpc/client.go +1 -1
  56. package/srpc/client.ts +4 -31
  57. package/srpc/common-rpc.go +15 -69
  58. package/srpc/common-rpc.ts +4 -77
  59. package/srpc/common-rpc_test.go +81 -0
  60. package/srpc/errors.go +26 -1
  61. package/srpc/handler.ts +54 -5
  62. package/srpc/index.ts +7 -4
  63. package/srpc/invoker-prefix.go +1 -1
  64. package/srpc/invoker.go +2 -2
  65. package/srpc/invoker.ts +20 -7
  66. package/srpc/message.go +1 -1
  67. package/srpc/msg-stream.go +1 -9
  68. package/srpc/mux-verbose.go +1 -1
  69. package/srpc/mux.go +1 -1
  70. package/srpc/rpcproto.pb.cc +4 -15
  71. package/srpc/rpcproto.pb.go +102 -419
  72. package/srpc/rpcproto.pb.h +0 -59
  73. package/srpc/rpcproto.pb.rs +0 -45
  74. package/srpc/rpcproto.pb.ts +0 -62
  75. package/srpc/rpcproto.proto +0 -16
  76. package/srpc/server-context.ts +55 -0
  77. package/srpc/server-rpc.go +1 -1
  78. package/srpc/server-rpc.ts +4 -6
  79. package/srpc/server.test.ts +94 -1
  80. package/srpc/stream-pipe.go +1 -1
  81. package/srpc/stream-rwc.go +1 -1
  82. package/srpc/stream.go +0 -20
  83. package/srpc/watchdog.test.ts +1 -0
  84. package/dist/rpcstream/receipt.test.d.ts +0 -1
  85. package/dist/rpcstream/receipt.test.js +0 -41
  86. package/dist/srpc/call-receipt.d.ts +0 -17
  87. package/dist/srpc/call-receipt.js +0 -106
  88. package/dist/srpc/call-receipt.test.d.ts +0 -1
  89. package/dist/srpc/call-receipt.test.js +0 -375
  90. package/dist/srpc/server-invocation.d.ts +0 -17
  91. package/dist/srpc/server-invocation.js +0 -37
  92. package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
  93. package/srpc/call-receipt-e2e_test.go +0 -111
  94. package/srpc/call-receipt.go +0 -112
  95. package/srpc/call-receipt.test.ts +0 -441
  96. package/srpc/call-receipt.ts +0 -131
  97. package/srpc/call-receipt_test.go +0 -536
  98. package/srpc/schema-ownership_test.go +0 -115
  99. package/srpc/server-invocation.go +0 -40
  100. 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;
@@ -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 { Echoer } from './echo_srpc.pb.js';
8
- export declare class EchoerServer implements Echoer {
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>): Promise<Message<EchoMsg>>;
14
- EchoBidiStream(request: MessageStream<EchoMsg>): MessageStream<EchoMsg>;
15
- RpcStream(request: MessageStream<RpcStreamPacket>): 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
  }
@@ -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 { EchoMsg, runClientTest } from '../../echo/index.js';
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 receiptMode = process.argv[2] === 'receipt';
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 [receipt <case>] <host:port>');
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
- if (receiptMode) {
79
- console.log(`Running held receipt test via TCP (${receiptCase})...`);
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, EchoMsg } from '../../echo/index.js';
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
- const packet = Packet.fromBinary(data.subarray(4));
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
- if (!receiptMode) {
133
- const echoer = new EchoerServer(server);
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;
@@ -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 instanceof MessagePort) {
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 instanceof MessagePort) {
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 instanceof MessagePort) {
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);
@@ -1,12 +1,10 @@
1
1
  import type { ProtoRpc } from './proto-rpc.js';
2
2
  import type { OpenStreamFunc } from './stream.js';
3
- import type { HeldCall, ReceiptRpc } from './call-receipt.js';
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>;
@@ -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, TerminalKind, type CallData, type CallStart } from './rpcproto.pb.js';
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>;