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