@trevrpc/trevrpc-js 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -18
- package/bin/protoc-gen-trevrpc-js.js +1 -1
- package/package.json +17 -12
- package/src/call.js +616 -40
- package/src/channel.js +190 -18
- package/src/client.js +555 -106
- package/src/compression.js +330 -0
- package/src/connection-metadata.js +114 -0
- package/src/datagram.js +357 -0
- package/src/framing.js +124 -110
- package/src/generator.js +147 -16
- package/src/index.d.ts +223 -17
- package/src/qmux/frames.js +262 -0
- package/src/qmux/params.js +104 -0
- package/src/qmux/session.js +740 -0
- package/src/race.js +90 -0
- package/src/status.js +1 -1
- package/src/stream-compression.js +298 -0
- package/src/varint.js +73 -0
- package/src/wire.js +47 -28
- package/src/zstd.d.ts +35 -0
- package/src/zstd.js +407 -0
package/src/client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Call, defaultCallOptions, mergeCallOptions } from "./call.js";
|
|
2
|
-
import { marshalMessage, unmarshalMessage } from "./framing.js";
|
|
1
|
+
import { Call, EarlyOk, defaultCallOptions, mergeCallOptions } from "./call.js";
|
|
2
|
+
import { FrameType, marshalMessage, unmarshalMessage } from "./framing.js";
|
|
3
3
|
import { validateMetadata } from "./metadata.js";
|
|
4
|
-
import { Code, internal, statusError } from "./status.js";
|
|
5
|
-
import {
|
|
4
|
+
import { Code, cancelled, internal, statusError } from "./status.js";
|
|
5
|
+
import { RpcAnswer, RpcHeaders, RpcKind, RpcStatus } from "./wire.js";
|
|
6
6
|
|
|
7
7
|
/** Creates a service client from a generated service descriptor. */
|
|
8
8
|
export function createServiceClient(transport, service, root, options = {}) {
|
|
@@ -10,19 +10,24 @@ export function createServiceClient(transport, service, root, options = {}) {
|
|
|
10
10
|
const client = {};
|
|
11
11
|
|
|
12
12
|
for (const [jsName, method] of Object.entries(service.methods)) {
|
|
13
|
+
const requestType = root.lookupType(method.inputType);
|
|
14
|
+
const responseType = root.lookupType(method.outputType);
|
|
13
15
|
const rpc = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
requestType
|
|
17
|
-
responseType
|
|
16
|
+
// The wire method path, computed once per method rather than per call.
|
|
17
|
+
path: `/${service.fullName}/${method.name}`,
|
|
18
|
+
requestType,
|
|
19
|
+
responseType,
|
|
20
|
+
// Full protobuf names, which pick the compression dictionaries.
|
|
21
|
+
types: { request: fullName(requestType), response: fullName(responseType) },
|
|
22
|
+
datagrams: datagramUse(method),
|
|
23
|
+
compressStream: streamCompressionUse(method),
|
|
24
|
+
compress: plainCompressionUse(method),
|
|
18
25
|
};
|
|
19
26
|
const resolve = (override) => mergeCallOptions(defaults, override);
|
|
20
27
|
|
|
21
28
|
switch (method.kind) {
|
|
22
29
|
case "unary":
|
|
23
30
|
client[jsName] = async (request, override) =>
|
|
24
|
-
(await unaryCall(transport, rpc, request, resolve(override))).message;
|
|
25
|
-
client[`${jsName}WithResponse`] = (request, override) =>
|
|
26
31
|
unaryCall(transport, rpc, request, resolve(override));
|
|
27
32
|
break;
|
|
28
33
|
case "serverStreaming":
|
|
@@ -44,29 +49,14 @@ export function createServiceClient(transport, service, root, options = {}) {
|
|
|
44
49
|
return client;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
/** Calls a unary RPC and returns the decoded response
|
|
52
|
+
/** Calls a unary RPC and returns the decoded response. */
|
|
48
53
|
export async function unaryCall(transport, rpc, request, options) {
|
|
49
54
|
const body = marshalMessage(rpc.requestType, request);
|
|
50
|
-
const call = await Call.open(transport,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
call.closeSend().catch(() => {});
|
|
56
|
-
response = await call.readFrame(RpcResponse);
|
|
57
|
-
validateResponseMetadata(response.metadata);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
throw call.fail(error);
|
|
60
|
-
}
|
|
61
|
-
call.finish();
|
|
62
|
-
|
|
63
|
-
if (response.status !== Code.Ok) {
|
|
64
|
-
throw statusError(response.status, response.message, response.metadata);
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
message: unmarshalMessage(rpc.responseType, response.body),
|
|
68
|
-
metadata: response.metadata,
|
|
69
|
-
};
|
|
55
|
+
const call = await Call.open(transport, requestFields(rpc, RpcKind.Unary), body, options, rpc);
|
|
56
|
+
const stream = new BidirectionalStreamingCall(call, rpc, { options });
|
|
57
|
+
// The response decides the outcome, so the request side finishes in the background.
|
|
58
|
+
stream.closeSend().catch(() => {});
|
|
59
|
+
return await receiveSingleResponse(stream, "unary");
|
|
70
60
|
}
|
|
71
61
|
|
|
72
62
|
/** Calls a server-streaming RPC and returns its response stream. */
|
|
@@ -74,10 +64,12 @@ export async function serverStreamingCall(transport, rpc, request, options) {
|
|
|
74
64
|
const body = marshalMessage(rpc.requestType, request);
|
|
75
65
|
const call = await Call.open(
|
|
76
66
|
transport,
|
|
77
|
-
|
|
67
|
+
requestFields(rpc, RpcKind.ServerStreaming),
|
|
68
|
+
body,
|
|
78
69
|
options,
|
|
70
|
+
rpc,
|
|
79
71
|
);
|
|
80
|
-
const stream = new BidirectionalStreamingCall(call, rpc);
|
|
72
|
+
const stream = new BidirectionalStreamingCall(call, rpc, { options });
|
|
81
73
|
stream.closeSend().catch(() => {});
|
|
82
74
|
return new ResponseStream(stream);
|
|
83
75
|
}
|
|
@@ -86,37 +78,168 @@ export async function serverStreamingCall(transport, rpc, request, options) {
|
|
|
86
78
|
export async function clientStreamingCall(transport, rpc, options) {
|
|
87
79
|
const call = await Call.open(
|
|
88
80
|
transport,
|
|
89
|
-
|
|
81
|
+
requestFields(rpc, RpcKind.ClientStreaming),
|
|
82
|
+
undefined,
|
|
90
83
|
options,
|
|
84
|
+
rpc,
|
|
85
|
+
);
|
|
86
|
+
return new ClientStreamingCall(
|
|
87
|
+
new BidirectionalStreamingCall(call, rpc, { streamsRequest: true, options }),
|
|
91
88
|
);
|
|
92
|
-
return new ClientStreamingCall(new BidirectionalStreamingCall(call, rpc));
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
/** Calls a bidirectional-streaming RPC. */
|
|
96
92
|
export async function bidirectionalStreamingCall(transport, rpc, options) {
|
|
97
93
|
const call = await Call.open(
|
|
98
94
|
transport,
|
|
99
|
-
|
|
95
|
+
requestFields(rpc, RpcKind.BidirectionalStreaming),
|
|
96
|
+
undefined,
|
|
100
97
|
options,
|
|
98
|
+
rpc,
|
|
101
99
|
);
|
|
102
|
-
return new BidirectionalStreamingCall(call, rpc);
|
|
100
|
+
return new BidirectionalStreamingCall(call, rpc, { streamsRequest: true, options });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Receives the single response message of a unary or client-streaming call, then its final
|
|
105
|
+
* status. A second message fails the call immediately; none at all is an internal error.
|
|
106
|
+
*/
|
|
107
|
+
async function receiveSingleResponse(stream, kind) {
|
|
108
|
+
let message;
|
|
109
|
+
let count = 0;
|
|
110
|
+
for (;;) {
|
|
111
|
+
const next = await stream.recv();
|
|
112
|
+
if (next === undefined) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
count += 1;
|
|
116
|
+
if (count > 1) {
|
|
117
|
+
const error = internal(`${kind} RPC returned more than one response message`);
|
|
118
|
+
await stream.close(error);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
message = next;
|
|
122
|
+
}
|
|
123
|
+
if (count !== 1) {
|
|
124
|
+
throw internal(`${kind} RPC returned no response message`);
|
|
125
|
+
}
|
|
126
|
+
await stream.status;
|
|
127
|
+
return message;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* How much of a response the call reads ahead of recv() while it is still uploading: at most this
|
|
132
|
+
* many frames or bytes are queued before reading pauses and the transport's flow control applies
|
|
133
|
+
* to a caller that does not receive. The byte bound is checked before each read, so the queue can
|
|
134
|
+
* exceed it by at most one frame, which is at most `maxFrameSize`. Without a bound a caller that
|
|
135
|
+
* only sends could be made to buffer a whole response.
|
|
136
|
+
*/
|
|
137
|
+
const ReadAheadFrames = 32;
|
|
138
|
+
const ReadAheadBytes = 1 << 20;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* How a method uses datagrams, or undefined if it does not: `send` for request messages, which
|
|
142
|
+
* needs a streaming request and `unreliableRequests`, and `receive` for response messages, which
|
|
143
|
+
* every call with a streaming response accepts. A method needs a flow if it marks either
|
|
144
|
+
* direction unreliable; the generator rejects marks on a direction that does not stream.
|
|
145
|
+
*/
|
|
146
|
+
function datagramUse({ kind, unreliableRequests, unreliableResponses }) {
|
|
147
|
+
const streamsRequest = kind === "clientStreaming" || kind === "bidirectionalStreaming";
|
|
148
|
+
const streamsResponse = kind === "serverStreaming" || kind === "bidirectionalStreaming";
|
|
149
|
+
if (!(unreliableRequests || unreliableResponses) || !(streamsRequest || streamsResponse)) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return { send: Boolean(unreliableRequests) && streamsRequest, receive: streamsResponse };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The directions a method may stream-compress, `{ requests, responses }`, or undefined for none:
|
|
157
|
+
* those its descriptor marks (the generator leaves out directions that do not stream and message
|
|
158
|
+
* types with redacted fields), except directions marked unreliable, whose messages mostly travel
|
|
159
|
+
* as datagrams.
|
|
160
|
+
*/
|
|
161
|
+
function streamCompressionUse({ compressStreamRequests, compressStreamResponses, ...method }) {
|
|
162
|
+
const requests = Boolean(compressStreamRequests) && !method.unreliableRequests;
|
|
163
|
+
const responses = Boolean(compressStreamResponses) && !method.unreliableResponses;
|
|
164
|
+
return requests || responses ? { requests, responses } : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The directions whose messages a method may compress without a dictionary, `{ requests,
|
|
169
|
+
* responses }`, or undefined for none: those its descriptor marks (the generator marks both for
|
|
170
|
+
* `COMPRESSION_MESSAGE` and `COMPRESSION_STREAM`, except message types with redacted fields).
|
|
171
|
+
*/
|
|
172
|
+
function plainCompressionUse({ compressRequests, compressResponses }) {
|
|
173
|
+
const requests = Boolean(compressRequests);
|
|
174
|
+
const responses = Boolean(compressResponses);
|
|
175
|
+
return requests || responses ? { requests, responses } : undefined;
|
|
103
176
|
}
|
|
104
177
|
|
|
105
178
|
/**
|
|
106
179
|
* A bidirectional-streaming RPC. Messages are received with recv() or async iteration. The call
|
|
107
180
|
* ends with the server's final status: a non-OK status makes recv() throw it, and `status`
|
|
108
|
-
* settles with the final status or the error that ended the call.
|
|
181
|
+
* settles with the final status or the error that ended the call. `headers` resolves once the
|
|
182
|
+
* first response frame after any ANSWER arrived; reading it starts that read, so headers are
|
|
183
|
+
* available before the first message.
|
|
184
|
+
*
|
|
185
|
+
* While the request side is open, the response is read ahead of recv() (see ReadAheadFrames), so
|
|
186
|
+
* a final status reaches a caller that is only sending. It stops the upload at once: pending and
|
|
187
|
+
* later sends fail with the non-OK status, or with Cancelled if the status is OK. A call that
|
|
188
|
+
* does not stream its request reads only what recv() or `headers` ask for.
|
|
189
|
+
*
|
|
190
|
+
* The final status settles the outcome as soon as it is read. Responses read before it are still
|
|
191
|
+
* delivered in order, and the call's deadline and abort signal no longer apply to it.
|
|
192
|
+
*
|
|
193
|
+
* The call options' `onResponseHeaders` is called with the headers when the first frame after
|
|
194
|
+
* ANSWER is read, and `onResponseMetadata` with the trailing metadata when the final status is
|
|
195
|
+
* OK. A callback that throws fails the call.
|
|
196
|
+
*
|
|
197
|
+
* A call that stream-compresses its requests optimistically and is refused by the server is
|
|
198
|
+
* replayed without that, transparently: the caller's sends and the response continue on the
|
|
199
|
+
* replay.
|
|
109
200
|
*/
|
|
110
201
|
export class BidirectionalStreamingCall {
|
|
111
202
|
#call;
|
|
112
203
|
#rpc;
|
|
204
|
+
/** Whether the request side streams, so that responses are read ahead of recv(). */
|
|
205
|
+
#streamsRequest;
|
|
113
206
|
/** Serializes recv() so concurrent callers never interleave frame reads. */
|
|
114
207
|
#reads = Promise.resolve();
|
|
115
|
-
|
|
116
|
-
#
|
|
117
|
-
#
|
|
208
|
+
/** Response messages read ahead of recv(), in order. */
|
|
209
|
+
#queue = [];
|
|
210
|
+
#queuedBytes = 0;
|
|
211
|
+
/**
|
|
212
|
+
* What ends the response, delivered by recv() after the queued messages: `{ status, error }`
|
|
213
|
+
* for the final status (error unless OK) or `{ error }` for a failed stream.
|
|
214
|
+
*/
|
|
215
|
+
#terminal;
|
|
216
|
+
/** An error that ended the call out of order, such as an abort; queued messages are dropped. */
|
|
217
|
+
#failure;
|
|
218
|
+
#failingInOrder = false;
|
|
219
|
+
#outcomeSettled = false;
|
|
220
|
+
#pumping = false;
|
|
221
|
+
/**
|
|
222
|
+
* The call's datagram flow when its responses may arrive as datagrams, else undefined. The flow
|
|
223
|
+
* connects once HEADERS (or the first frame that implies them) was read; until then it buffers.
|
|
224
|
+
*/
|
|
225
|
+
#flow;
|
|
226
|
+
/** Receivers waiting for a frame. */
|
|
227
|
+
#demand = 0;
|
|
228
|
+
#headersWanted = false;
|
|
229
|
+
#sawAnswer = false;
|
|
230
|
+
#sawHeaders = false;
|
|
231
|
+
#sawMessage = false;
|
|
232
|
+
/** Settles on every change a waiter may care about. */
|
|
233
|
+
#changed = deferred();
|
|
118
234
|
#resolveStatus;
|
|
119
235
|
#rejectStatus;
|
|
236
|
+
#resolveHeaders;
|
|
237
|
+
#rejectHeaders;
|
|
238
|
+
#headers;
|
|
239
|
+
#onResponseHeaders;
|
|
240
|
+
#onResponseMetadata;
|
|
241
|
+
/** Whether the headers were handed to onResponseHeaders. */
|
|
242
|
+
#headersReported = false;
|
|
120
243
|
|
|
121
244
|
/** The final status, or the error that ended the call. */
|
|
122
245
|
status = new Promise((resolve, reject) => {
|
|
@@ -124,27 +247,101 @@ export class BidirectionalStreamingCall {
|
|
|
124
247
|
this.#rejectStatus = reject;
|
|
125
248
|
});
|
|
126
249
|
|
|
127
|
-
constructor(call, rpc) {
|
|
250
|
+
constructor(call, rpc, { streamsRequest = false, options = {} } = {}) {
|
|
128
251
|
this.#call = call;
|
|
129
252
|
this.#rpc = rpc;
|
|
253
|
+
this.#streamsRequest = streamsRequest;
|
|
254
|
+
this.#onResponseHeaders = options.onResponseHeaders;
|
|
255
|
+
this.#onResponseMetadata = options.onResponseMetadata;
|
|
256
|
+
this.#flow = rpc.datagrams?.receive ? call.flow : undefined;
|
|
130
257
|
this.status.catch(() => {});
|
|
131
|
-
|
|
132
|
-
|
|
258
|
+
this.#headers = new Promise((resolve, reject) => {
|
|
259
|
+
this.#resolveHeaders = resolve;
|
|
260
|
+
this.#rejectHeaders = reject;
|
|
133
261
|
});
|
|
262
|
+
this.#headers.catch(() => {});
|
|
263
|
+
call.signal.addEventListener(
|
|
264
|
+
"abort",
|
|
265
|
+
() => {
|
|
266
|
+
if (!this.#failingInOrder) {
|
|
267
|
+
this.#settleFailure(call.signal.reason);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{ once: true },
|
|
271
|
+
);
|
|
134
272
|
if (call.signal.aborted) {
|
|
135
273
|
this.#settleFailure(call.signal.reason);
|
|
274
|
+
} else {
|
|
275
|
+
this.#kick();
|
|
136
276
|
}
|
|
137
277
|
}
|
|
138
278
|
|
|
139
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* The response headers, empty when the server sent none. Settles when the first frame after
|
|
281
|
+
* ANSWER arrives, or rejects with the error that ended the call first.
|
|
282
|
+
*/
|
|
283
|
+
get headers() {
|
|
284
|
+
this.#headersWanted = true;
|
|
285
|
+
this.#kick();
|
|
286
|
+
return this.#headers;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Sends one request message. Fails with the server's final status if the server ended the
|
|
291
|
+
* call, as soon as it is known; with Cancelled if that status is OK; and with the error that
|
|
292
|
+
* ended the call otherwise.
|
|
293
|
+
*/
|
|
140
294
|
async send(message) {
|
|
141
295
|
const body = marshalMessage(this.#rpc.requestType, message);
|
|
142
|
-
|
|
296
|
+
try {
|
|
297
|
+
await this.#call.sendMessage(body);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw await this.#explainUploadFailure(error);
|
|
300
|
+
}
|
|
143
301
|
}
|
|
144
302
|
|
|
145
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* Closes the request stream while keeping the response stream readable. Resolves if the server
|
|
305
|
+
* already ended the call: its final status is reported by recv().
|
|
306
|
+
*/
|
|
146
307
|
async closeSend() {
|
|
147
|
-
|
|
308
|
+
try {
|
|
309
|
+
await this.#call.closeSend();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const reason = await this.#explainUploadFailure(error);
|
|
312
|
+
if (this.#terminal?.status === undefined) {
|
|
313
|
+
throw reason;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Sends every message of requests, an iterable or async iterable, then closes the request
|
|
320
|
+
* stream. If the server ends the call first, the rest is not sent and nothing is thrown: the
|
|
321
|
+
* response side reports the outcome. A send that fails otherwise rejects as send() does, and
|
|
322
|
+
* requests that throw cancel the call with that error and reject with it.
|
|
323
|
+
*/
|
|
324
|
+
async sendAll(requests) {
|
|
325
|
+
let sendFailure;
|
|
326
|
+
try {
|
|
327
|
+
for await (const request of requests) {
|
|
328
|
+
try {
|
|
329
|
+
await this.send(request);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (this.#terminal?.status !== undefined) {
|
|
332
|
+
return; // the server ended the call; the response side reports how
|
|
333
|
+
}
|
|
334
|
+
sendFailure = error;
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error !== sendFailure) {
|
|
340
|
+
await this.close(error);
|
|
341
|
+
}
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
await this.closeSend();
|
|
148
345
|
}
|
|
149
346
|
|
|
150
347
|
/** Receives the next response message, or undefined after the final OK status. */
|
|
@@ -156,8 +353,9 @@ export class BidirectionalStreamingCall {
|
|
|
156
353
|
|
|
157
354
|
/** Cancels the call unless it already finished. */
|
|
158
355
|
async close(reason) {
|
|
159
|
-
if (
|
|
356
|
+
if (this.#terminal === undefined || this.#queue.length > 0) {
|
|
160
357
|
this.#call.cancel(reason);
|
|
358
|
+
this.#settleFailure(this.#call.signal.reason);
|
|
161
359
|
}
|
|
162
360
|
}
|
|
163
361
|
|
|
@@ -175,55 +373,300 @@ export class BidirectionalStreamingCall {
|
|
|
175
373
|
}
|
|
176
374
|
|
|
177
375
|
async #recv() {
|
|
178
|
-
|
|
179
|
-
|
|
376
|
+
const item = await this.#take();
|
|
377
|
+
if (item.payload !== undefined) {
|
|
378
|
+
try {
|
|
379
|
+
return unmarshalMessage(this.#rpc.responseType, item.payload);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
throw this.#settleFailure(this.#call.fail(error));
|
|
382
|
+
}
|
|
180
383
|
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
384
|
+
if (item.error !== undefined) {
|
|
385
|
+
throw item.error;
|
|
183
386
|
}
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
184
389
|
|
|
185
|
-
|
|
390
|
+
/** Takes the next queued message, or the end of the response once the queue is empty. */
|
|
391
|
+
async #take() {
|
|
392
|
+
for (;;) {
|
|
393
|
+
if (this.#failure !== undefined) {
|
|
394
|
+
throw this.#failure;
|
|
395
|
+
}
|
|
396
|
+
const payload = this.#queue.shift();
|
|
397
|
+
if (payload !== undefined) {
|
|
398
|
+
this.#queuedBytes -= payload.byteLength;
|
|
399
|
+
// Room for more read-ahead.
|
|
400
|
+
this.#kick();
|
|
401
|
+
return { payload };
|
|
402
|
+
}
|
|
403
|
+
if (this.#terminal !== undefined) {
|
|
404
|
+
// A failure of the stream itself surfaces here, in order, after the responses before it.
|
|
405
|
+
if (this.#terminal.status === undefined) {
|
|
406
|
+
throw this.#settleFailure(this.#terminal.error);
|
|
407
|
+
}
|
|
408
|
+
return this.#terminal;
|
|
409
|
+
}
|
|
410
|
+
const changed = this.#changed.promise;
|
|
411
|
+
this.#demand += 1;
|
|
412
|
+
this.#kick();
|
|
413
|
+
try {
|
|
414
|
+
await changed;
|
|
415
|
+
} finally {
|
|
416
|
+
this.#demand -= 1;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Starts reading the response unless it is running or has nothing to read for. */
|
|
422
|
+
#kick() {
|
|
423
|
+
if (!this.#pumping && this.#wantsRead()) {
|
|
424
|
+
this.#pumping = true;
|
|
425
|
+
// The pump handles the errors of the response itself; anything else is a bug that ends the call.
|
|
426
|
+
this.#pump().catch((error) => this.#settleFailure(this.#call.fail(error)));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
#wantsRead() {
|
|
431
|
+
if (this.#terminal !== undefined || this.#failure !== undefined) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
if (this.#demand > 0 && this.#queue.length === 0) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
if (this.#headersWanted && !this.#sawHeaders && !this.#sawMessage) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
// A call with a datagram flow always reads ahead, so datagrams are released after HEADERS
|
|
441
|
+
// even if nobody receives.
|
|
442
|
+
return (
|
|
443
|
+
((this.#streamsRequest && this.#call.uploadOpen) || this.#flow !== undefined) &&
|
|
444
|
+
this.#queue.length < ReadAheadFrames &&
|
|
445
|
+
this.#queuedBytes < ReadAheadBytes
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Reads response events into the queue for as long as someone wants them. */
|
|
450
|
+
async #pump() {
|
|
186
451
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
452
|
+
while (this.#wantsRead()) {
|
|
453
|
+
let event;
|
|
454
|
+
try {
|
|
455
|
+
event = await this.#readEvent();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
this.#endWithError(error);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (this.#failure !== undefined) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (event.type === FrameType.Message) {
|
|
464
|
+
this.#queue.push(event.payload);
|
|
465
|
+
this.#queuedBytes += event.payload.byteLength;
|
|
466
|
+
} else {
|
|
467
|
+
this.#endWithStatus(event.status);
|
|
468
|
+
}
|
|
469
|
+
this.#notify();
|
|
190
470
|
}
|
|
191
|
-
|
|
192
|
-
|
|
471
|
+
} finally {
|
|
472
|
+
this.#pumping = false;
|
|
473
|
+
this.#notify();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** The final status arrived: it is the outcome, and it ends the upload. */
|
|
478
|
+
#endWithStatus({ code, message, metadata }) {
|
|
479
|
+
if (code === Code.Ok && this.#onResponseMetadata !== undefined) {
|
|
480
|
+
try {
|
|
481
|
+
this.#onResponseMetadata(metadata);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
this.#endWithError(error);
|
|
484
|
+
return;
|
|
193
485
|
}
|
|
194
|
-
validateResponseMetadata(frame.metadata);
|
|
195
|
-
} catch (error) {
|
|
196
|
-
throw this.#settleFailure(this.#call.fail(error));
|
|
197
486
|
}
|
|
487
|
+
const error = code === Code.Ok ? undefined : statusError(code, message, metadata);
|
|
488
|
+
this.#terminal = { status: { code, message, metadata }, error };
|
|
489
|
+
this.#call.finish(error);
|
|
490
|
+
if (error === undefined) {
|
|
491
|
+
this.#outcomeSettled = true;
|
|
492
|
+
this.#resolveStatus({ code, message, metadata });
|
|
493
|
+
} else {
|
|
494
|
+
this.#rejectOutcome(error);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
198
497
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
498
|
+
/**
|
|
499
|
+
* The response stream failed or ended without a final status. Responses read before are still
|
|
500
|
+
* delivered; the error surfaces when recv() reaches it. The call is torn down now, so sends
|
|
501
|
+
* fail with this error.
|
|
502
|
+
*/
|
|
503
|
+
#endWithError(error) {
|
|
504
|
+
if (this.#call.signal.aborted) {
|
|
505
|
+
return; // an abort already decided the outcome
|
|
506
|
+
}
|
|
507
|
+
this.#failingInOrder = true;
|
|
508
|
+
const reason = this.#call.fail(error);
|
|
509
|
+
this.#failingInOrder = false;
|
|
510
|
+
this.#terminal = { error: reason };
|
|
511
|
+
this.#rejectOutcome(reason);
|
|
512
|
+
this.#notify();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Settles the headers on their first frame, and hands them to onResponseHeaders once. */
|
|
516
|
+
#settleHeaders(metadata) {
|
|
517
|
+
this.#resolveHeaders(metadata);
|
|
518
|
+
if (!this.#headersReported) {
|
|
519
|
+
this.#headersReported = true;
|
|
520
|
+
this.#onResponseHeaders?.(metadata);
|
|
206
521
|
}
|
|
207
|
-
this.#resolveStatus({ code: Code.Ok, message: frame.message, metadata: frame.metadata });
|
|
208
|
-
return undefined;
|
|
209
522
|
}
|
|
210
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Reads the next MESSAGE or STATUS event, consuming ANSWER and HEADERS on the way. Enforces the
|
|
526
|
+
* response grammar `[ANSWER] [HEADERS] MESSAGE* STATUS`, and that an optimistic call's response
|
|
527
|
+
* starts with ANSWER or STATUS. A ZSTD_MESSAGE or ZSTD_STREAM_MESSAGE is a MESSAGE,
|
|
528
|
+
* decompressed. ANSWER is not the headers: they settle with the first frame after it.
|
|
529
|
+
*/
|
|
530
|
+
async #readEvent() {
|
|
531
|
+
for (;;) {
|
|
532
|
+
const frame = await this.#call.readFrame();
|
|
533
|
+
if (frame === null) {
|
|
534
|
+
throw this.#call.uploadFailure ?? internal("response stream ended before final status");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (
|
|
538
|
+
frame.type !== FrameType.Status &&
|
|
539
|
+
frame.type !== FrameType.Answer &&
|
|
540
|
+
this.#call.replayable
|
|
541
|
+
) {
|
|
542
|
+
throw internal("optimistic call answered without an ANSWER frame");
|
|
543
|
+
}
|
|
544
|
+
switch (frame.type) {
|
|
545
|
+
case FrameType.Answer: {
|
|
546
|
+
if (this.#sawAnswer || this.#sawHeaders || this.#sawMessage) {
|
|
547
|
+
throw internal(
|
|
548
|
+
this.#sawAnswer
|
|
549
|
+
? "duplicate ANSWER frame"
|
|
550
|
+
: `ANSWER frame after ${this.#sawMessage ? "MESSAGE" : "HEADERS"}`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
this.#sawAnswer = true;
|
|
554
|
+
this.#call.learnAnswers(unmarshalMessage(RpcAnswer, frame.payload));
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
case FrameType.Headers: {
|
|
558
|
+
if (this.#sawHeaders || this.#sawMessage) {
|
|
559
|
+
throw internal(
|
|
560
|
+
this.#sawHeaders ? "duplicate HEADERS frame" : "HEADERS frame after MESSAGE",
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
const headers = unmarshalMessage(RpcHeaders, frame.payload);
|
|
564
|
+
validateResponseMetadata(headers.metadata, "headers");
|
|
565
|
+
this.#sawHeaders = true;
|
|
566
|
+
this.#settleHeaders(headers.metadata);
|
|
567
|
+
this.#flow?.connect(this.#onDatagram);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
case FrameType.Message:
|
|
571
|
+
case FrameType.ZstdMessage:
|
|
572
|
+
case FrameType.ZstdStreamMessage: {
|
|
573
|
+
const payload = this.#call.receivedMessage(frame);
|
|
574
|
+
this.#sawMessage = true;
|
|
575
|
+
this.#settleHeaders({});
|
|
576
|
+
this.#flow?.connect(this.#onDatagram);
|
|
577
|
+
return { type: FrameType.Message, payload };
|
|
578
|
+
}
|
|
579
|
+
case FrameType.Status: {
|
|
580
|
+
const status = unmarshalMessage(RpcStatus, frame.payload);
|
|
581
|
+
if (status.refusedStreamCompression && this.#call.replayable) {
|
|
582
|
+
// The server refused the optimistic call before running anything for it: the call
|
|
583
|
+
// continues on a replay, and its response is read from there.
|
|
584
|
+
// A call with a datagram flow is never optimistic, but its replay, on a session
|
|
585
|
+
// that may differ after a reconnect, can get one.
|
|
586
|
+
await this.#call.replay();
|
|
587
|
+
this.#flow = this.#rpc.datagrams?.receive ? this.#call.flow : undefined;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
validateResponseMetadata(status.metadata, "trailing metadata");
|
|
591
|
+
this.#settleHeaders({});
|
|
592
|
+
this.#flow?.connect(this.#onDatagram);
|
|
593
|
+
return { type: FrameType.Status, status };
|
|
594
|
+
}
|
|
595
|
+
default:
|
|
596
|
+
throw internal(`unexpected frame type ${frame.type} on a response stream`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Queues a response message that arrived as a datagram, in arrival order with the stream's
|
|
603
|
+
* messages. It never blocks: a message is dropped when the call ended or its queue is full.
|
|
604
|
+
*/
|
|
605
|
+
#onDatagram = (payload) => {
|
|
606
|
+
if (
|
|
607
|
+
this.#terminal !== undefined ||
|
|
608
|
+
this.#failure !== undefined ||
|
|
609
|
+
payload.byteLength > this.#call.maxFrameSize ||
|
|
610
|
+
this.#queue.length >= ReadAheadFrames ||
|
|
611
|
+
this.#queuedBytes + payload.byteLength > ReadAheadBytes
|
|
612
|
+
) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
this.#queue.push(payload);
|
|
616
|
+
this.#queuedBytes += payload.byteLength;
|
|
617
|
+
this.#notify();
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
/** Ends the call out of order: receivers fail with `error` without draining the queue. */
|
|
211
621
|
#settleFailure(error) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
622
|
+
this.#failure ??= error;
|
|
623
|
+
this.#rejectOutcome(error);
|
|
624
|
+
this.#notify();
|
|
625
|
+
return this.#failure;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
#rejectOutcome(error) {
|
|
629
|
+
if (!this.#outcomeSettled) {
|
|
630
|
+
this.#outcomeSettled = true;
|
|
215
631
|
this.#rejectStatus(error);
|
|
632
|
+
this.#rejectHeaders(error);
|
|
216
633
|
}
|
|
217
|
-
return this.#error;
|
|
218
634
|
}
|
|
219
635
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
636
|
+
/** Wakes everything waiting for a change of the response. */
|
|
637
|
+
#notify() {
|
|
638
|
+
const { resolve } = this.#changed;
|
|
639
|
+
this.#changed = deferred();
|
|
640
|
+
resolve();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Resolves when the response outcome is known, the call failed, or no read is in progress, which
|
|
645
|
+
* is when nothing more will be learned without the caller receiving.
|
|
646
|
+
*/
|
|
647
|
+
async #outcomeKnown() {
|
|
648
|
+
while (this.#terminal === undefined && this.#failure === undefined && this.#pumping) {
|
|
649
|
+
await this.#changed.promise;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* The error a failed request-side operation reports. A request side that the transport failed
|
|
655
|
+
* on its own, typically because the server stopped reading it, says little: the server's
|
|
656
|
+
* final status, or the failure of the response stream, explains it, so those are awaited.
|
|
657
|
+
*/
|
|
658
|
+
async #explainUploadFailure(error) {
|
|
659
|
+
if (error !== this.#call.uploadFailure) {
|
|
660
|
+
return error;
|
|
226
661
|
}
|
|
662
|
+
await this.#outcomeKnown();
|
|
663
|
+
if (this.#terminal?.status !== undefined) {
|
|
664
|
+
return this.#terminal.error ?? cancelled(EarlyOk);
|
|
665
|
+
}
|
|
666
|
+
if (this.#call.signal.aborted) {
|
|
667
|
+
return this.#call.signal.reason;
|
|
668
|
+
}
|
|
669
|
+
return this.#terminal?.error ?? error;
|
|
227
670
|
}
|
|
228
671
|
}
|
|
229
672
|
|
|
@@ -240,30 +683,15 @@ export class ClientStreamingCall {
|
|
|
240
683
|
return this.#call.send(message);
|
|
241
684
|
}
|
|
242
685
|
|
|
243
|
-
/**
|
|
244
|
-
|
|
245
|
-
return
|
|
686
|
+
/** Sends every message of requests, then closes the request stream; see the bidirectional call. */
|
|
687
|
+
sendAll(requests) {
|
|
688
|
+
return this.#call.sendAll(requests);
|
|
246
689
|
}
|
|
247
690
|
|
|
248
|
-
/** Closes the request stream and returns the final response
|
|
249
|
-
async
|
|
691
|
+
/** Closes the request stream and returns the final response. */
|
|
692
|
+
async closeAndRecv() {
|
|
250
693
|
this.#call.closeSend().catch(() => {});
|
|
251
|
-
|
|
252
|
-
let message;
|
|
253
|
-
let count = 0;
|
|
254
|
-
for (;;) {
|
|
255
|
-
const next = await this.#call.recv();
|
|
256
|
-
if (next === undefined) {
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
message ??= next;
|
|
260
|
-
count += 1;
|
|
261
|
-
}
|
|
262
|
-
if (count !== 1) {
|
|
263
|
-
throw internal(`client-streaming RPC returned ${count} response messages instead of one`);
|
|
264
|
-
}
|
|
265
|
-
const status = await this.#call.status;
|
|
266
|
-
return { message, metadata: status.metadata };
|
|
694
|
+
return await receiveSingleResponse(this.#call, "client-streaming");
|
|
267
695
|
}
|
|
268
696
|
|
|
269
697
|
/** Cancels the call unless it already finished. */
|
|
@@ -285,6 +713,14 @@ export class ResponseStream {
|
|
|
285
713
|
return this.#call.status;
|
|
286
714
|
}
|
|
287
715
|
|
|
716
|
+
/**
|
|
717
|
+
* The response headers, empty when the server sent none. Settles when the first frame after
|
|
718
|
+
* ANSWER arrives, even before the first message is received, or rejects with the call error.
|
|
719
|
+
*/
|
|
720
|
+
get headers() {
|
|
721
|
+
return this.#call.headers;
|
|
722
|
+
}
|
|
723
|
+
|
|
288
724
|
/** Cancels the call unless it already finished. */
|
|
289
725
|
close(reason) {
|
|
290
726
|
return this.#call.close(reason);
|
|
@@ -295,14 +731,27 @@ export class ResponseStream {
|
|
|
295
731
|
}
|
|
296
732
|
}
|
|
297
733
|
|
|
298
|
-
function
|
|
299
|
-
|
|
734
|
+
function deferred() {
|
|
735
|
+
let resolve;
|
|
736
|
+
const promise = new Promise((resolvePromise) => {
|
|
737
|
+
resolve = resolvePromise;
|
|
738
|
+
});
|
|
739
|
+
return { promise, resolve };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function requestFields(rpc, kind) {
|
|
743
|
+
return { method: rpc.path, kind };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** The full name of a protobuf.js message type, without the leading dot. */
|
|
747
|
+
function fullName(type) {
|
|
748
|
+
return type.fullName.replace(/^\./u, "");
|
|
300
749
|
}
|
|
301
750
|
|
|
302
|
-
function validateResponseMetadata(metadata) {
|
|
751
|
+
function validateResponseMetadata(metadata, what) {
|
|
303
752
|
try {
|
|
304
753
|
validateMetadata(metadata);
|
|
305
754
|
} catch (error) {
|
|
306
|
-
throw internal(`invalid response
|
|
755
|
+
throw internal(`invalid response ${what}: ${error.statusMessage ?? error.message}`);
|
|
307
756
|
}
|
|
308
757
|
}
|