@trevrpc/trevrpc-js 0.2.3 → 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 +127 -46
- package/package.json +12 -45
- package/src/bytes.js +10 -0
- package/src/call.js +520 -0
- package/src/channel.js +258 -288
- package/src/client.js +543 -1254
- package/src/datagram.js +336 -0
- package/src/framing.js +151 -548
- package/src/generator.js +66 -109
- package/src/index.d.ts +235 -463
- package/src/index.js +4 -3
- 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 +24 -70
- package/src/varint.js +73 -0
- package/src/wire.js +21 -89
- package/examples/greeter/README.md +0 -35
- package/examples/greeter/client.js +0 -172
- package/examples/greeter/greeter.node.trevrpc.d.ts +0 -53
- package/examples/greeter/greeter.node.trevrpc.js +0 -9
- package/examples/greeter/greeter.proto +0 -18
- package/examples/greeter/greeter.trevrpc.d.ts +0 -55
- package/examples/greeter/greeter.trevrpc.js +0 -103
- package/examples/greeter/index.html +0 -140
- package/examples/greeter/static-server.js +0 -168
- package/src/advanced.d.ts +0 -29
- package/src/advanced.js +0 -1
- package/src/browser-channel.js +0 -49
- package/src/connect.js +0 -6
- package/src/connect.node.js +0 -6
- package/src/index.node.d.ts +0 -10
- package/src/index.node.js +0 -3
- package/src/native-loader.js +0 -118
- package/src/node-advanced.d.ts +0 -27
- package/src/node-advanced.js +0 -1
- package/src/node-generated.d.ts +0 -24
- package/src/node-generated.js +0 -126
- package/src/node-index.d.ts +0 -1
- package/src/node-index.js +0 -7
- package/src/node.d.ts +0 -298
- package/src/node.js +0 -1456
- package/src/runtime.js +0 -66
- package/src/server-response.js +0 -49
- package/src/timer.js +0 -29
- package/src/webtransport.js +0 -639
package/src/client.js
CHANGED
|
@@ -1,467 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
TrevRpcError,
|
|
7
|
-
cancelled,
|
|
8
|
-
deadlineExceeded,
|
|
9
|
-
internal,
|
|
10
|
-
invalidArgument,
|
|
11
|
-
isOkStatus,
|
|
12
|
-
resourceExhausted,
|
|
13
|
-
statusFromResponse,
|
|
14
|
-
unavailable,
|
|
15
|
-
} from "./status.js";
|
|
16
|
-
import { scheduleTimeout } from "./timer.js";
|
|
17
|
-
import { RpcKind, RpcStreamFrameKind, WireVersion } from "./wire.js";
|
|
18
|
-
|
|
19
|
-
const RequestStreamBatchSize = 16;
|
|
20
|
-
const PreparedCallOptions = Symbol("trevrpc.preparedCallOptions");
|
|
21
|
-
const EmptyMetadata = Object.freeze(Object.create(null));
|
|
22
|
-
const WriterTerminalNone = 0;
|
|
23
|
-
const WriterTerminalOk = 1;
|
|
24
|
-
const WriterTerminalNonOk = 2;
|
|
25
|
-
|
|
26
|
-
/** Internal stream-state failure with a stable diagnostic reason. */
|
|
27
|
-
class StreamStateError extends TrevRpcError {
|
|
28
|
-
constructor(reason, message) {
|
|
29
|
-
super(Code.Internal, message);
|
|
30
|
-
this.name = "StreamStateError";
|
|
31
|
-
this.reason = reason;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Coordinates request writer completion with response stream cleanup. */
|
|
36
|
-
export class RequestWriterSettlement {
|
|
37
|
-
constructor(
|
|
38
|
-
normalizeError = (error) => error,
|
|
39
|
-
isCleanupError = () => false,
|
|
40
|
-
isTerminalError = () => false,
|
|
41
|
-
terminalErrorRequiresTerminalFirst = false,
|
|
42
|
-
) {
|
|
43
|
-
this._terminal = WriterTerminalNone;
|
|
44
|
-
this._cleanupStarted = false;
|
|
45
|
-
this._hasWriterError = false;
|
|
46
|
-
this._writerError = undefined;
|
|
47
|
-
this._errorAfterCleanup = false;
|
|
48
|
-
this._errorAfterTerminal = false;
|
|
49
|
-
this._errorReported = false;
|
|
50
|
-
this._normalizeError = normalizeError;
|
|
51
|
-
this._isCleanupError = isCleanupError;
|
|
52
|
-
this._isTerminalError = isTerminalError;
|
|
53
|
-
this._terminalErrorRequiresTerminalFirst = terminalErrorRequiresTerminalFirst;
|
|
54
|
-
this.done = Promise.resolve();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
track(writerTask) {
|
|
58
|
-
this.done = Promise.resolve(writerTask).then(
|
|
59
|
-
() => {},
|
|
60
|
-
(error) => this.recordError(error),
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
recordError(error) {
|
|
65
|
-
if (this._hasWriterError) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
this._hasWriterError = true;
|
|
69
|
-
this._writerError = this._normalizeError(error);
|
|
70
|
-
this._errorAfterCleanup = this._cleanupStarted;
|
|
71
|
-
this._errorAfterTerminal = this._terminal !== WriterTerminalNone;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
recordTerminal(statusCode) {
|
|
75
|
-
if (this._terminal === WriterTerminalNone) {
|
|
76
|
-
this._terminal = statusCode === Code.Ok ? WriterTerminalOk : WriterTerminalNonOk;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
beginCleanup() {
|
|
81
|
-
this._cleanupStarted = true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async finish() {
|
|
85
|
-
await this.done;
|
|
86
|
-
if (
|
|
87
|
-
!this._hasWriterError ||
|
|
88
|
-
this._terminal === WriterTerminalNonOk ||
|
|
89
|
-
(this._terminal === WriterTerminalOk &&
|
|
90
|
-
(!this._terminalErrorRequiresTerminalFirst || this._errorAfterTerminal) &&
|
|
91
|
-
this._isTerminalError(this._writerError)) ||
|
|
92
|
-
(this._errorAfterCleanup && this._isCleanupError(this._writerError)) ||
|
|
93
|
-
this._errorReported
|
|
94
|
-
) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this._errorReported = true;
|
|
99
|
-
throw this._writerError;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Returns the default client call options. */
|
|
104
|
-
export function defaultCallOptions() {
|
|
105
|
-
return {
|
|
106
|
-
timeoutMs: undefined,
|
|
107
|
-
maxResponseBodySize: DefaultMaxFrameSize,
|
|
108
|
-
maxResponseMessages: 4096,
|
|
109
|
-
maxResponseStreamBodySize: 16 * 1024 * 1024,
|
|
110
|
-
streamIdleTimeoutMs: 30_000,
|
|
111
|
-
metadata: {},
|
|
112
|
-
signal: undefined,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Merges call options and normalizes metadata. */
|
|
117
|
-
export function mergeCallOptions(base = {}, override = {}) {
|
|
118
|
-
if (isPreparedCallOptions(base) && !hasDefinedOptions(override)) {
|
|
119
|
-
return base;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const merged = { ...defaultCallOptions() };
|
|
123
|
-
copyDefined(merged, base);
|
|
124
|
-
copyDefined(merged, override);
|
|
125
|
-
merged.metadata = mergeMetadata(base, override);
|
|
126
|
-
return markPreparedCallOptions(merged);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function preparedCallOptions(options = {}) {
|
|
130
|
-
return isPreparedCallOptions(options) ? options : mergeCallOptions(options);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Calls a unary RPC and decodes the protobuf response. */
|
|
134
|
-
export async function unary(
|
|
135
|
-
transport,
|
|
136
|
-
service,
|
|
137
|
-
method,
|
|
138
|
-
requestType,
|
|
139
|
-
responseType,
|
|
140
|
-
request,
|
|
141
|
-
options = {},
|
|
142
|
-
) {
|
|
143
|
-
return (
|
|
144
|
-
await unaryWithResponse(transport, service, method, requestType, responseType, request, options)
|
|
145
|
-
).message;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Calls a unary RPC and returns the decoded response envelope. */
|
|
149
|
-
export async function unaryWithResponse(
|
|
150
|
-
transport,
|
|
151
|
-
service,
|
|
152
|
-
method,
|
|
153
|
-
requestType,
|
|
154
|
-
responseType,
|
|
155
|
-
request,
|
|
156
|
-
options = {},
|
|
157
|
-
) {
|
|
158
|
-
const callOptions = preparedCallOptions(options);
|
|
159
|
-
const abortScope = callAbortScope(callOptions);
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
throwIfAborted(abortScope.signal);
|
|
163
|
-
const requestBody = marshalMessage(requestType, request);
|
|
164
|
-
const rpcRequest = prepareClientRequest(
|
|
165
|
-
service,
|
|
166
|
-
method,
|
|
167
|
-
RpcKind.Unary,
|
|
168
|
-
requestBody,
|
|
169
|
-
abortScope.options,
|
|
170
|
-
);
|
|
171
|
-
const response = await waitForOperation(
|
|
172
|
-
transport.call(rpcRequest, abortScope.options),
|
|
173
|
-
abortScope.signal,
|
|
174
|
-
);
|
|
175
|
-
const responseStatus = validateResponse(response, callOptions.maxResponseBodySize);
|
|
176
|
-
abortScope.terminal();
|
|
177
|
-
if (!isOkStatus(responseStatus)) {
|
|
178
|
-
throw responseStatus;
|
|
179
|
-
}
|
|
180
|
-
return {
|
|
181
|
-
message: unmarshalMessage(responseType, response.body ?? new Uint8Array(0)),
|
|
182
|
-
metadata: response.metadata ?? {},
|
|
183
|
-
};
|
|
184
|
-
} finally {
|
|
185
|
-
abortScope.cleanup();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Calls a server-streaming RPC and returns a decoded response stream. */
|
|
190
|
-
export async function serverStreaming(
|
|
191
|
-
transport,
|
|
192
|
-
service,
|
|
193
|
-
method,
|
|
194
|
-
requestType,
|
|
195
|
-
responseType,
|
|
196
|
-
request,
|
|
197
|
-
options = {},
|
|
198
|
-
) {
|
|
199
|
-
const callOptions = preparedCallOptions(options);
|
|
200
|
-
const abortScope = callAbortScope(callOptions);
|
|
201
|
-
try {
|
|
202
|
-
throwIfAborted(abortScope.signal);
|
|
203
|
-
const requestBody = marshalMessage(requestType, request);
|
|
204
|
-
const rpcRequest = prepareClientRequest(
|
|
205
|
-
service,
|
|
206
|
-
method,
|
|
207
|
-
RpcKind.ServerStreaming,
|
|
208
|
-
requestBody,
|
|
209
|
-
abortScope.options,
|
|
210
|
-
);
|
|
211
|
-
const frames = await waitForOperation(
|
|
212
|
-
transport.streamingCall(rpcRequest, emptyAsyncIterable(), abortScope.options),
|
|
213
|
-
abortScope.signal,
|
|
214
|
-
);
|
|
215
|
-
return responseMessageStream(frames, responseType, abortScope.options, abortScope);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
abortScope.cleanup();
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** Calls a client-streaming RPC and returns a sendable call object. */
|
|
223
|
-
export async function clientStreaming(
|
|
224
|
-
transport,
|
|
225
|
-
service,
|
|
226
|
-
method,
|
|
227
|
-
requestType,
|
|
228
|
-
responseType,
|
|
229
|
-
options = {},
|
|
230
|
-
) {
|
|
231
|
-
const callOptions = preparedCallOptions(options);
|
|
232
|
-
const abortScope = callAbortScope(callOptions, { requestCancellation: true });
|
|
233
|
-
const requests = new RequestQueue(abortScope.signal);
|
|
234
|
-
try {
|
|
235
|
-
const rpcRequest = prepareClientRequest(
|
|
236
|
-
service,
|
|
237
|
-
method,
|
|
238
|
-
RpcKind.ClientStreaming,
|
|
239
|
-
new Uint8Array(0),
|
|
240
|
-
abortScope.options,
|
|
241
|
-
);
|
|
242
|
-
const frames = await waitForOperation(
|
|
243
|
-
transport.streamingCall(
|
|
244
|
-
rpcRequest,
|
|
245
|
-
encodeRequestStream(requestType, requests, abortScope.signal),
|
|
246
|
-
abortScope.options,
|
|
247
|
-
),
|
|
248
|
-
abortScope.signal,
|
|
249
|
-
);
|
|
250
|
-
return new ClientStreamingCall(
|
|
251
|
-
requests,
|
|
252
|
-
responseMessageStream(frames, responseType, abortScope.options, abortScope, (error) =>
|
|
253
|
-
requests.close(error),
|
|
254
|
-
),
|
|
255
|
-
);
|
|
256
|
-
} catch (error) {
|
|
257
|
-
requests.close(error);
|
|
258
|
-
abortScope.cleanup();
|
|
259
|
-
throw error;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Calls a bidirectional-streaming RPC and returns a sendable call object. */
|
|
264
|
-
export async function bidirectionalStreaming(
|
|
265
|
-
transport,
|
|
266
|
-
service,
|
|
267
|
-
method,
|
|
268
|
-
requestType,
|
|
269
|
-
responseType,
|
|
270
|
-
options = {},
|
|
271
|
-
) {
|
|
272
|
-
const callOptions = preparedCallOptions(options);
|
|
273
|
-
const abortScope = callAbortScope(callOptions, { requestCancellation: true });
|
|
274
|
-
const requests = new RequestQueue(abortScope.signal);
|
|
275
|
-
try {
|
|
276
|
-
const rpcRequest = prepareClientRequest(
|
|
277
|
-
service,
|
|
278
|
-
method,
|
|
279
|
-
RpcKind.BidirectionalStreaming,
|
|
280
|
-
new Uint8Array(0),
|
|
281
|
-
abortScope.options,
|
|
282
|
-
);
|
|
283
|
-
const frames = await waitForOperation(
|
|
284
|
-
transport.streamingCall(
|
|
285
|
-
rpcRequest,
|
|
286
|
-
encodeRequestStream(requestType, requests, abortScope.signal),
|
|
287
|
-
abortScope.options,
|
|
288
|
-
),
|
|
289
|
-
abortScope.signal,
|
|
290
|
-
);
|
|
291
|
-
return new BidirectionalStreamingCall(
|
|
292
|
-
requests,
|
|
293
|
-
responseMessageStream(frames, responseType, abortScope.options, abortScope, (error) =>
|
|
294
|
-
requests.close(error),
|
|
295
|
-
),
|
|
296
|
-
);
|
|
297
|
-
} catch (error) {
|
|
298
|
-
requests.close(error);
|
|
299
|
-
abortScope.cleanup();
|
|
300
|
-
throw error;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/** A client-streaming RPC call. */
|
|
305
|
-
export class ClientStreamingCall {
|
|
306
|
-
constructor(requests, responses) {
|
|
307
|
-
this._requests = requests;
|
|
308
|
-
this._responses = responses;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** Sends one request message. */
|
|
312
|
-
send(request) {
|
|
313
|
-
return this._requests.send(request);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** Sends multiple request messages. */
|
|
317
|
-
sendMany(requests) {
|
|
318
|
-
return this._requests.sendMany(requests);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** Closes the request stream. */
|
|
322
|
-
closeSend() {
|
|
323
|
-
this._requests.close();
|
|
324
|
-
return Promise.resolve();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/** Closes the request stream and returns the final response. */
|
|
328
|
-
async closeAndRecv() {
|
|
329
|
-
this._requests.close();
|
|
330
|
-
return await readUnaryResponseFromStream(this._responses);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Closes the request stream and returns the final response envelope. */
|
|
334
|
-
async closeAndRecvWithResponse() {
|
|
335
|
-
this._requests.close();
|
|
336
|
-
return await readUnaryResponseEnvelopeFromStream(this._responses);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/** Releases call resources without waiting for the response. */
|
|
340
|
-
async close(reason) {
|
|
341
|
-
const error = cancellationReason(reason, "request stream cancelled");
|
|
342
|
-
this._requests.close(error);
|
|
343
|
-
await this._responses.close(error);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/** A bidirectional-streaming RPC call. */
|
|
348
|
-
export class BidirectionalStreamingCall {
|
|
349
|
-
constructor(requests, responses) {
|
|
350
|
-
this._requests = requests;
|
|
351
|
-
this._responses = responses;
|
|
352
|
-
this.status = responses.status;
|
|
353
|
-
this._iterator = responses[Symbol.asyncIterator]();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/** Sends one request message. */
|
|
357
|
-
send(request) {
|
|
358
|
-
return this._requests.send(request);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Sends multiple request messages. */
|
|
362
|
-
sendMany(requests) {
|
|
363
|
-
return this._requests.sendMany(requests);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/** Receives one response message, or undefined after the response stream completes. */
|
|
367
|
-
async recv() {
|
|
368
|
-
const result = await this._iterator.next();
|
|
369
|
-
return result.done ? undefined : result.value;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/** Closes the request stream while keeping the response stream readable. */
|
|
373
|
-
closeSend() {
|
|
374
|
-
this._requests.close();
|
|
375
|
-
return Promise.resolve();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/** Releases call resources without waiting for more responses. */
|
|
379
|
-
async close(reason) {
|
|
380
|
-
const error = cancellationReason(reason, "request stream cancelled");
|
|
381
|
-
this._requests.close(error);
|
|
382
|
-
await this._responses.close(error);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
[Symbol.asyncIterator]() {
|
|
386
|
-
return {
|
|
387
|
-
next: async () => {
|
|
388
|
-
const value = await this.recv();
|
|
389
|
-
return value === undefined ? { done: true, value: undefined } : { done: false, value };
|
|
390
|
-
},
|
|
391
|
-
return: async () => {
|
|
392
|
-
await this.close();
|
|
393
|
-
return { done: true, value: undefined };
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
}
|
|
1
|
+
import { Call, defaultCallOptions, mergeCallOptions } from "./call.js";
|
|
2
|
+
import { FrameType, marshalMessage, unmarshalMessage } from "./framing.js";
|
|
3
|
+
import { validateMetadata } from "./metadata.js";
|
|
4
|
+
import { Code, cancelled, internal, statusError } from "./status.js";
|
|
5
|
+
import { RpcHeaders, RpcKind, RpcStatus } from "./wire.js";
|
|
398
6
|
|
|
399
7
|
/** Creates a service client from a generated service descriptor. */
|
|
400
8
|
export function createServiceClient(transport, service, root, options = {}) {
|
|
9
|
+
const defaults = mergeCallOptions(defaultCallOptions(), options);
|
|
401
10
|
const client = {};
|
|
402
|
-
const baseOptions = mergeCallOptions(options);
|
|
403
11
|
|
|
404
12
|
for (const [jsName, method] of Object.entries(service.methods)) {
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
13
|
+
const rpc = {
|
|
14
|
+
// The wire method path, computed once per method rather than per call.
|
|
15
|
+
path: `/${service.fullName}/${method.name}`,
|
|
16
|
+
requestType: root.lookupType(method.inputType),
|
|
17
|
+
responseType: root.lookupType(method.outputType),
|
|
18
|
+
datagrams: datagramUse(method),
|
|
19
|
+
};
|
|
20
|
+
const resolve = (override) => mergeCallOptions(defaults, override);
|
|
408
21
|
|
|
409
22
|
switch (method.kind) {
|
|
410
23
|
case "unary":
|
|
411
|
-
client[jsName] = (request, override) =>
|
|
412
|
-
|
|
413
|
-
transport,
|
|
414
|
-
service.fullName,
|
|
415
|
-
method.name,
|
|
416
|
-
requestType,
|
|
417
|
-
responseType,
|
|
418
|
-
request,
|
|
419
|
-
callOptions(override),
|
|
420
|
-
);
|
|
421
|
-
client[`${jsName}WithResponse`] = (request, override) =>
|
|
422
|
-
unaryWithResponse(
|
|
423
|
-
transport,
|
|
424
|
-
service.fullName,
|
|
425
|
-
method.name,
|
|
426
|
-
requestType,
|
|
427
|
-
responseType,
|
|
428
|
-
request,
|
|
429
|
-
callOptions(override),
|
|
430
|
-
);
|
|
24
|
+
client[jsName] = async (request, override) =>
|
|
25
|
+
unaryCall(transport, rpc, request, resolve(override));
|
|
431
26
|
break;
|
|
432
27
|
case "serverStreaming":
|
|
433
28
|
client[jsName] = (request, override) =>
|
|
434
|
-
|
|
435
|
-
transport,
|
|
436
|
-
service.fullName,
|
|
437
|
-
method.name,
|
|
438
|
-
requestType,
|
|
439
|
-
responseType,
|
|
440
|
-
request,
|
|
441
|
-
callOptions(override),
|
|
442
|
-
);
|
|
29
|
+
serverStreamingCall(transport, rpc, request, resolve(override));
|
|
443
30
|
break;
|
|
444
31
|
case "clientStreaming":
|
|
445
|
-
client[jsName] = (override) =>
|
|
446
|
-
clientStreaming(
|
|
447
|
-
transport,
|
|
448
|
-
service.fullName,
|
|
449
|
-
method.name,
|
|
450
|
-
requestType,
|
|
451
|
-
responseType,
|
|
452
|
-
callOptions(override),
|
|
453
|
-
);
|
|
32
|
+
client[jsName] = (override) => clientStreamingCall(transport, rpc, resolve(override));
|
|
454
33
|
break;
|
|
455
34
|
case "bidirectionalStreaming":
|
|
456
35
|
client[jsName] = (override) =>
|
|
457
|
-
|
|
458
|
-
transport,
|
|
459
|
-
service.fullName,
|
|
460
|
-
method.name,
|
|
461
|
-
requestType,
|
|
462
|
-
responseType,
|
|
463
|
-
callOptions(override),
|
|
464
|
-
);
|
|
36
|
+
bidirectionalStreamingCall(transport, rpc, resolve(override));
|
|
465
37
|
break;
|
|
466
38
|
default:
|
|
467
39
|
throw internal(`unsupported generated RPC kind ${JSON.stringify(method.kind)}`);
|
|
@@ -471,927 +43,644 @@ export function createServiceClient(transport, service, root, options = {}) {
|
|
|
471
43
|
return client;
|
|
472
44
|
}
|
|
473
45
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
metadata,
|
|
483
|
-
version: WireVersion,
|
|
484
|
-
};
|
|
485
|
-
const timeout = timeoutNanos(options.timeoutMs);
|
|
486
|
-
|
|
487
|
-
if (kind !== RpcKind.Unary) {
|
|
488
|
-
request.kind = kind;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (timeout != null) {
|
|
492
|
-
request.timeoutNanos = timeout;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return request;
|
|
46
|
+
/** Calls a unary RPC and returns the decoded response. */
|
|
47
|
+
export async function unaryCall(transport, rpc, request, options) {
|
|
48
|
+
const body = marshalMessage(rpc.requestType, request);
|
|
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");
|
|
496
54
|
}
|
|
497
55
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return
|
|
56
|
+
/** Calls a server-streaming RPC and returns its response stream. */
|
|
57
|
+
export async function serverStreamingCall(transport, rpc, request, options) {
|
|
58
|
+
const body = marshalMessage(rpc.requestType, request);
|
|
59
|
+
const call = await Call.open(
|
|
60
|
+
transport,
|
|
61
|
+
requestFields(rpc, RpcKind.ServerStreaming),
|
|
62
|
+
body,
|
|
63
|
+
options,
|
|
64
|
+
rpc.datagrams,
|
|
65
|
+
);
|
|
66
|
+
const stream = new BidirectionalStreamingCall(call, rpc, { options });
|
|
67
|
+
stream.closeSend().catch(() => {});
|
|
68
|
+
return new ResponseStream(stream);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Calls a client-streaming RPC and returns a call for sending requests. */
|
|
72
|
+
export async function clientStreamingCall(transport, rpc, options) {
|
|
73
|
+
const call = await Call.open(
|
|
74
|
+
transport,
|
|
75
|
+
requestFields(rpc, RpcKind.ClientStreaming),
|
|
76
|
+
undefined,
|
|
77
|
+
options,
|
|
78
|
+
rpc.datagrams,
|
|
79
|
+
);
|
|
80
|
+
return new ClientStreamingCall(
|
|
81
|
+
new BidirectionalStreamingCall(call, rpc, { streamsRequest: true, options }),
|
|
82
|
+
);
|
|
511
83
|
}
|
|
512
84
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
85
|
+
/** Calls a bidirectional-streaming RPC. */
|
|
86
|
+
export async function bidirectionalStreamingCall(transport, rpc, options) {
|
|
87
|
+
const call = await Call.open(
|
|
88
|
+
transport,
|
|
89
|
+
requestFields(rpc, RpcKind.BidirectionalStreaming),
|
|
90
|
+
undefined,
|
|
91
|
+
options,
|
|
92
|
+
rpc.datagrams,
|
|
93
|
+
);
|
|
94
|
+
return new BidirectionalStreamingCall(call, rpc, { streamsRequest: true, options });
|
|
95
|
+
}
|
|
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
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A bidirectional-streaming RPC. Messages are received with recv() or async iteration. The call
|
|
153
|
+
* ends with the server's final status: a non-OK status makes recv() throw it, and `status`
|
|
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.
|
|
169
|
+
*/
|
|
170
|
+
export class BidirectionalStreamingCall {
|
|
171
|
+
#call;
|
|
172
|
+
#rpc;
|
|
173
|
+
/** Whether the request side streams, so that responses are read ahead of recv(). */
|
|
174
|
+
#streamsRequest;
|
|
175
|
+
/** Serializes recv() so concurrent callers never interleave frame reads. */
|
|
176
|
+
#reads = Promise.resolve();
|
|
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();
|
|
202
|
+
#resolveStatus;
|
|
203
|
+
#rejectStatus;
|
|
204
|
+
#resolveHeaders;
|
|
205
|
+
#rejectHeaders;
|
|
206
|
+
#headers;
|
|
207
|
+
#onResponseHeaders;
|
|
208
|
+
#onResponseMetadata;
|
|
209
|
+
/** Whether the headers were handed to onResponseHeaders. */
|
|
210
|
+
#headersReported = false;
|
|
211
|
+
|
|
212
|
+
/** The final status, or the error that ended the call. */
|
|
213
|
+
status = new Promise((resolve, reject) => {
|
|
214
|
+
this.#resolveStatus = resolve;
|
|
215
|
+
this.#rejectStatus = reject;
|
|
216
|
+
});
|
|
522
217
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
this
|
|
526
|
-
this
|
|
527
|
-
this
|
|
528
|
-
this
|
|
529
|
-
this
|
|
530
|
-
this._useBodyBatches = typeof this._iterator.nextBodyBatch === "function";
|
|
531
|
-
this._counters = { messages: 0, streamBodySize: 0 };
|
|
532
|
-
this._messages = [];
|
|
533
|
-
this._pendingError = null;
|
|
534
|
-
this._pendingStatus = null;
|
|
535
|
-
this._complete = false;
|
|
536
|
-
this._failure = null;
|
|
537
|
-
this._cleanupPromise = null;
|
|
538
|
-
this._statusSettled = false;
|
|
539
|
-
this._operation = Promise.resolve();
|
|
540
|
-
this.status = new Promise((resolve, reject) => {
|
|
541
|
-
this._resolveStatus = resolve;
|
|
542
|
-
this._rejectStatus = reject;
|
|
543
|
-
});
|
|
218
|
+
constructor(call, rpc, { streamsRequest = false, options = {} } = {}) {
|
|
219
|
+
this.#call = call;
|
|
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;
|
|
544
225
|
this.status.catch(() => {});
|
|
545
|
-
this
|
|
546
|
-
|
|
547
|
-
this
|
|
548
|
-
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
|
|
226
|
+
this.#headers = new Promise((resolve, reject) => {
|
|
227
|
+
this.#resolveHeaders = resolve;
|
|
228
|
+
this.#rejectHeaders = reject;
|
|
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
|
+
);
|
|
240
|
+
if (call.signal.aborted) {
|
|
241
|
+
this.#settleFailure(call.signal.reason);
|
|
552
242
|
} else {
|
|
553
|
-
this
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
[Symbol.asyncIterator]() {
|
|
558
|
-
return this;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
next() {
|
|
562
|
-
return this._serialize(() => this._next());
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async return() {
|
|
566
|
-
await this.close();
|
|
567
|
-
return { done: true, value: undefined };
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
async close(reason) {
|
|
571
|
-
if (this._complete) {
|
|
572
|
-
await this._cleanupPromise;
|
|
573
|
-
return;
|
|
243
|
+
this.#kick();
|
|
574
244
|
}
|
|
575
|
-
const error = cancellationReason(reason, "response stream cancelled");
|
|
576
|
-
this._abortScope?.abort(error);
|
|
577
|
-
this._recordFailure(error);
|
|
578
|
-
await this._cleanup(error);
|
|
579
245
|
}
|
|
580
246
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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;
|
|
585
255
|
}
|
|
586
256
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
+
*/
|
|
262
|
+
async send(message) {
|
|
263
|
+
const body = marshalMessage(this.#rpc.requestType, message);
|
|
595
264
|
try {
|
|
596
|
-
|
|
597
|
-
if (this._messages.length > 0) {
|
|
598
|
-
return { done: false, value: this._messages.shift() };
|
|
599
|
-
}
|
|
600
|
-
if (this._pendingError != null) {
|
|
601
|
-
const error = this._pendingError;
|
|
602
|
-
this._pendingError = null;
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
|
-
if (this._pendingStatus != null) {
|
|
606
|
-
const frame = this._pendingStatus;
|
|
607
|
-
this._pendingStatus = null;
|
|
608
|
-
await this._finishStatus(frame);
|
|
609
|
-
if (this._failure != null) {
|
|
610
|
-
throw this._failure;
|
|
611
|
-
}
|
|
612
|
-
return { done: true, value: undefined };
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
const result = await this._readNext();
|
|
616
|
-
if (result.done) {
|
|
617
|
-
throw new StreamStateError(
|
|
618
|
-
"missing_terminal_status",
|
|
619
|
-
"response stream ended before final status",
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
this._consumeBatch(result.value);
|
|
623
|
-
}
|
|
265
|
+
await this.#call.sendMessage(body);
|
|
624
266
|
} catch (error) {
|
|
625
|
-
|
|
626
|
-
await this._cleanup(primary);
|
|
627
|
-
throw primary;
|
|
267
|
+
throw await this.#explainUploadFailure(error);
|
|
628
268
|
}
|
|
629
269
|
}
|
|
630
270
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
644
|
-
const frame = value[index];
|
|
645
|
-
if (frame == null) {
|
|
646
|
-
const error = new StreamStateError(
|
|
647
|
-
"missing_terminal_status",
|
|
648
|
-
"response stream ended before final status",
|
|
649
|
-
);
|
|
650
|
-
if (this._messages.length > 0) {
|
|
651
|
-
this._pendingError = error;
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
throw error;
|
|
655
|
-
}
|
|
656
|
-
if (frame.kind === RpcStreamFrameKind.Message) {
|
|
657
|
-
const body = frame.body ?? new Uint8Array(0);
|
|
658
|
-
validateResponseMessageBody(body, this._options, this._counters);
|
|
659
|
-
this._messages.push(unmarshalMessage(this._responseType, body));
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
if (frame.kind === RpcStreamFrameKind.Status) {
|
|
663
|
-
if (index + 1 < value.length) {
|
|
664
|
-
throw new StreamStateError(
|
|
665
|
-
"trailing_frame",
|
|
666
|
-
"response stream contained a frame after final status",
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
this._pendingStatus = this._claimStatus(frame);
|
|
670
|
-
continue;
|
|
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
|
+
*/
|
|
275
|
+
async closeSend() {
|
|
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;
|
|
671
282
|
}
|
|
672
|
-
const error = invalidArgument("response stream contained an unknown frame kind");
|
|
673
|
-
error.reason = "unsupported_frame_kind";
|
|
674
|
-
throw error;
|
|
675
283
|
}
|
|
676
284
|
}
|
|
677
285
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
status.code === Code.Ok
|
|
687
|
-
? null
|
|
688
|
-
: statusFromResponse({
|
|
689
|
-
status: status.code,
|
|
690
|
-
message: status.message,
|
|
691
|
-
metadata: status.metadata,
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
if (!this._abortScope?.terminal()) {
|
|
695
|
-
throw signalAbortError(this._options.signal);
|
|
696
|
-
}
|
|
697
|
-
this._onTerminal(terminalError);
|
|
698
|
-
return { status, terminalError };
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
async _finishStatus(terminal) {
|
|
702
|
-
const { status, terminalError } = terminal;
|
|
703
|
-
let end;
|
|
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;
|
|
704
294
|
try {
|
|
705
|
-
|
|
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
|
+
}
|
|
706
306
|
} catch (error) {
|
|
707
|
-
if (
|
|
708
|
-
|
|
307
|
+
if (error !== sendFailure) {
|
|
308
|
+
await this.close(error);
|
|
709
309
|
}
|
|
710
|
-
|
|
711
|
-
"trailing_frame",
|
|
712
|
-
"response stream did not end cleanly after final status",
|
|
713
|
-
);
|
|
714
|
-
trailing.cause = error;
|
|
715
|
-
throw trailing;
|
|
716
|
-
}
|
|
717
|
-
if (!end.done) {
|
|
718
|
-
throw new StreamStateError(
|
|
719
|
-
"trailing_frame",
|
|
720
|
-
"response stream contained a frame after final status",
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
await this._cleanup(terminalError);
|
|
725
|
-
if (terminalError != null) {
|
|
726
|
-
this._recordFailure(terminalError);
|
|
727
|
-
throw terminalError;
|
|
310
|
+
throw error;
|
|
728
311
|
}
|
|
729
|
-
this.
|
|
730
|
-
this._settleStatus(true, status);
|
|
312
|
+
await this.closeSend();
|
|
731
313
|
}
|
|
732
314
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
this._options.signal,
|
|
739
|
-
this._abortScope?.abort,
|
|
740
|
-
)
|
|
741
|
-
: nextFrameBatchWithTimeout(
|
|
742
|
-
this._iterator,
|
|
743
|
-
this._options.streamIdleTimeoutMs,
|
|
744
|
-
this._options.signal,
|
|
745
|
-
this._abortScope?.abort,
|
|
746
|
-
);
|
|
315
|
+
/** Receives the next response message, or undefined after the final OK status. */
|
|
316
|
+
recv() {
|
|
317
|
+
const next = this.#reads.then(() => this.#recv());
|
|
318
|
+
this.#reads = next.catch(() => {});
|
|
319
|
+
return next;
|
|
747
320
|
}
|
|
748
321
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
this.
|
|
322
|
+
/** Cancels the call unless it already finished. */
|
|
323
|
+
async close(reason) {
|
|
324
|
+
if (this.#terminal === undefined || this.#queue.length > 0) {
|
|
325
|
+
this.#call.cancel(reason);
|
|
326
|
+
this.#settleFailure(this.#call.signal.reason);
|
|
753
327
|
}
|
|
754
|
-
return this._failure ?? error;
|
|
755
328
|
}
|
|
756
329
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
330
|
+
[Symbol.asyncIterator]() {
|
|
331
|
+
return {
|
|
332
|
+
next: async () => {
|
|
333
|
+
const value = await this.recv();
|
|
334
|
+
return value === undefined ? { done: true, value: undefined } : { done: false, value };
|
|
335
|
+
},
|
|
336
|
+
return: async () => {
|
|
337
|
+
await this.close();
|
|
338
|
+
return { done: true, value: undefined };
|
|
339
|
+
},
|
|
340
|
+
};
|
|
767
341
|
}
|
|
768
342
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
this._cleanupPromise = (async () => {
|
|
774
|
-
this._options.signal?.removeEventListener?.("abort", this._onAbort);
|
|
343
|
+
async #recv() {
|
|
344
|
+
const item = await this.#take();
|
|
345
|
+
if (item.payload !== undefined) {
|
|
775
346
|
try {
|
|
776
|
-
|
|
777
|
-
await this._iterator.return();
|
|
778
|
-
}
|
|
347
|
+
return unmarshalMessage(this.#rpc.responseType, item.payload);
|
|
779
348
|
} catch (error) {
|
|
780
|
-
|
|
781
|
-
throw error;
|
|
782
|
-
}
|
|
783
|
-
} finally {
|
|
784
|
-
this._abortScope?.cleanup();
|
|
349
|
+
throw this.#settleFailure(this.#call.fail(error));
|
|
785
350
|
}
|
|
786
|
-
})();
|
|
787
|
-
return this._cleanupPromise;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function validateResponseMessageBody(body, options, counters) {
|
|
792
|
-
if (options.maxResponseMessages >= 0 && counters.messages >= options.maxResponseMessages) {
|
|
793
|
-
throw resourceExhausted(
|
|
794
|
-
`response stream exceeded maximum of ${options.maxResponseMessages} messages`,
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
if (body.byteLength > options.maxResponseBodySize) {
|
|
799
|
-
throw new FrameTooLargeError(body.byteLength, options.maxResponseBodySize);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
counters.messages += 1;
|
|
803
|
-
counters.streamBodySize = saturatingAdd(counters.streamBodySize, body.byteLength);
|
|
804
|
-
if (
|
|
805
|
-
options.maxResponseStreamBodySize >= 0 &&
|
|
806
|
-
counters.streamBodySize > options.maxResponseStreamBodySize
|
|
807
|
-
) {
|
|
808
|
-
throw resourceExhausted(
|
|
809
|
-
`response stream exceeded maximum body size of ${options.maxResponseStreamBodySize} bytes`,
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
async function nextBodyBatchWithTimeout(iterator, idleTimeoutMs, signal, onTimeout) {
|
|
815
|
-
return await withTimeout(
|
|
816
|
-
iterator.nextBodyBatch(),
|
|
817
|
-
idleTimeoutMs,
|
|
818
|
-
() => unavailable("response stream idle timeout"),
|
|
819
|
-
onTimeout,
|
|
820
|
-
signal,
|
|
821
|
-
);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
async function nextFrameBatchWithTimeout(iterator, idleTimeoutMs, signal, onTimeout) {
|
|
825
|
-
const hasBatchReader = typeof iterator.nextBatch === "function";
|
|
826
|
-
const result = await withTimeout(
|
|
827
|
-
hasBatchReader ? iterator.nextBatch() : iterator.next(),
|
|
828
|
-
idleTimeoutMs,
|
|
829
|
-
() => unavailable("response stream idle timeout"),
|
|
830
|
-
onTimeout,
|
|
831
|
-
signal,
|
|
832
|
-
);
|
|
833
|
-
return hasBatchReader || result.done ? result : { done: false, value: [result.value] };
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function isMalformedTrailingRead(error) {
|
|
837
|
-
return (
|
|
838
|
-
error instanceof FrameTooLargeError ||
|
|
839
|
-
error?.reason === "malformed_protobuf" ||
|
|
840
|
-
error?.reason === "incomplete_frame"
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function validateResponseMetadata(metadata) {
|
|
845
|
-
try {
|
|
846
|
-
validateMetadata(metadata);
|
|
847
|
-
} catch (error) {
|
|
848
|
-
throw new StreamStateError(
|
|
849
|
-
"invalid_metadata",
|
|
850
|
-
`invalid response metadata: ${error.statusMessage ?? error.message}`,
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
async function readUnaryResponseFromStream(stream) {
|
|
856
|
-
return (await readUnaryResponseEnvelopeFromStream(stream)).message;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
async function readUnaryResponseEnvelopeFromStream(stream) {
|
|
860
|
-
let firstMessage;
|
|
861
|
-
let messageCount = 0;
|
|
862
|
-
for await (const message of stream) {
|
|
863
|
-
if (messageCount === 0) {
|
|
864
|
-
firstMessage = message;
|
|
865
351
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (messageCount === 0) {
|
|
871
|
-
throw new StreamStateError(
|
|
872
|
-
"response_cardinality",
|
|
873
|
-
"response stream ended without a response message",
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
if (messageCount !== 1) {
|
|
877
|
-
throw new StreamStateError(
|
|
878
|
-
"response_cardinality",
|
|
879
|
-
"client-streaming RPC returned more than one response message",
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
return { message: firstMessage, metadata: status.metadata ?? {} };
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function timeoutNanos(timeoutMs) {
|
|
887
|
-
if (timeoutMs == null) {
|
|
888
|
-
return null;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
892
|
-
throw invalidArgument("RPC timeout must be a non-negative finite number of milliseconds");
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const nanos = BigInt(Math.ceil(timeoutMs)) * 1_000_000n;
|
|
896
|
-
return String(nanos === 0n ? 1n : nanos);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function withTimeout(promise, timeoutMs, makeError, onTimeout, signal) {
|
|
900
|
-
if (timeoutMs == null && signal == null) {
|
|
901
|
-
return promise;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
if (timeoutMs != null && !Number.isFinite(timeoutMs)) {
|
|
905
|
-
throw invalidArgument("RPC timeout must be a finite number of milliseconds");
|
|
352
|
+
if (item.error !== undefined) {
|
|
353
|
+
throw item.error;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
906
356
|
}
|
|
907
357
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
signal?.removeEventListener?.("abort", onAbort);
|
|
914
|
-
};
|
|
915
|
-
const settle = (complete, value) => {
|
|
916
|
-
if (settled) {
|
|
917
|
-
return;
|
|
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;
|
|
918
363
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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;
|
|
937
385
|
}
|
|
938
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
939
386
|
}
|
|
940
|
-
|
|
941
|
-
promise.then(
|
|
942
|
-
(value) => {
|
|
943
|
-
settle(resolve, value);
|
|
944
|
-
},
|
|
945
|
-
(error) => {
|
|
946
|
-
settle(reject, error);
|
|
947
|
-
},
|
|
948
|
-
);
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
function signalAbortError(signal) {
|
|
953
|
-
const reason = signal?.reason;
|
|
954
|
-
return reason instanceof TrevRpcError ? reason : cancelled("RPC cancelled");
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
function cancellationReason(reason, fallback) {
|
|
958
|
-
if (reason instanceof TrevRpcError && reason.code === Code.Cancelled) {
|
|
959
|
-
return reason;
|
|
960
|
-
}
|
|
961
|
-
const error = cancelled(fallback);
|
|
962
|
-
if (reason !== undefined) {
|
|
963
|
-
Object.defineProperty(error, "cause", { configurable: true, value: reason });
|
|
964
|
-
}
|
|
965
|
-
return error;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
function normalizeAbortReason(reason) {
|
|
969
|
-
return reason instanceof TrevRpcError ? reason : cancellationReason(reason, "RPC cancelled");
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function throwIfAborted(signal) {
|
|
973
|
-
if (signal?.aborted) {
|
|
974
|
-
throw signalAbortError(signal);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
function waitForOperation(promise, signal) {
|
|
979
|
-
return withTimeout(promise, null, null, null, signal);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
function callAbortScope(options, { requestCancellation = false } = {}) {
|
|
983
|
-
const needsTransportSignal = options.signal != null || options.timeoutMs != null;
|
|
984
|
-
if (!needsTransportSignal && !requestCancellation) {
|
|
985
|
-
return {
|
|
986
|
-
options,
|
|
987
|
-
signal: undefined,
|
|
988
|
-
abort() {
|
|
989
|
-
return false;
|
|
990
|
-
},
|
|
991
|
-
terminal() {
|
|
992
|
-
return true;
|
|
993
|
-
},
|
|
994
|
-
cleanup() {},
|
|
995
|
-
};
|
|
996
387
|
}
|
|
997
388
|
|
|
998
|
-
|
|
999
|
-
|
|
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
|
+
}
|
|
1000
396
|
}
|
|
1001
397
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const deadlineError =
|
|
1005
|
-
options.timeoutMs == null ? null : deadlineExceeded("RPC deadline exceeded");
|
|
1006
|
-
let settlement = "active";
|
|
1007
|
-
let cleaned = false;
|
|
1008
|
-
let cancelDeadline = () => {};
|
|
1009
|
-
|
|
1010
|
-
const clearDeadline = () => {
|
|
1011
|
-
cancelDeadline();
|
|
1012
|
-
cancelDeadline = () => {};
|
|
1013
|
-
};
|
|
1014
|
-
const removeParent = () => parentSignal?.removeEventListener?.("abort", onParentAbort);
|
|
1015
|
-
const abort = (reason) => {
|
|
1016
|
-
if (settlement !== "active") {
|
|
398
|
+
#wantsRead() {
|
|
399
|
+
if (this.#terminal !== undefined || this.#failure !== undefined) {
|
|
1017
400
|
return false;
|
|
1018
401
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
removeParent();
|
|
1022
|
-
controller.abort(normalizeAbortReason(reason));
|
|
1023
|
-
return true;
|
|
1024
|
-
};
|
|
1025
|
-
const terminal = () => {
|
|
1026
|
-
if (settlement !== "active") {
|
|
1027
|
-
return false;
|
|
402
|
+
if (this.#demand > 0 && this.#queue.length === 0) {
|
|
403
|
+
return true;
|
|
1028
404
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
removeParent();
|
|
1032
|
-
return true;
|
|
1033
|
-
};
|
|
1034
|
-
const onParentAbort = () => abort(parentSignal.reason);
|
|
1035
|
-
|
|
1036
|
-
if (parentSignal != null) {
|
|
1037
|
-
if (parentSignal.aborted) {
|
|
1038
|
-
onParentAbort();
|
|
1039
|
-
} else {
|
|
1040
|
-
parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
405
|
+
if (this.#headersWanted && !this.#sawHeaders && !this.#sawMessage) {
|
|
406
|
+
return true;
|
|
1041
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
|
+
);
|
|
1042
415
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
416
|
+
|
|
417
|
+
/** Reads response events into the queue for as long as someone wants them. */
|
|
418
|
+
async #pump() {
|
|
419
|
+
try {
|
|
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();
|
|
438
|
+
}
|
|
439
|
+
} finally {
|
|
440
|
+
this.#pumping = false;
|
|
441
|
+
this.#notify();
|
|
442
|
+
}
|
|
1045
443
|
}
|
|
1046
444
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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);
|
|
1054
452
|
return;
|
|
1055
453
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
class RequestQueue {
|
|
1064
|
-
constructor(signal) {
|
|
1065
|
-
this._pending = [];
|
|
1066
|
-
this._waiter = undefined;
|
|
1067
|
-
this._closed = false;
|
|
1068
|
-
this._error = undefined;
|
|
1069
|
-
this._signal = signal;
|
|
1070
|
-
this._onAbort = () => this.close(signalAbortError(signal));
|
|
1071
|
-
if (signal?.aborted) {
|
|
1072
|
-
this._onAbort();
|
|
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 });
|
|
1073
461
|
} else {
|
|
1074
|
-
|
|
462
|
+
this.#rejectOutcome(error);
|
|
1075
463
|
}
|
|
1076
464
|
}
|
|
1077
465
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
|
1085
474
|
}
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
return new Promise((resolve, reject) => {
|
|
1093
|
-
let remaining = batch.length;
|
|
1094
|
-
const resolveOne = () => {
|
|
1095
|
-
remaining -= 1;
|
|
1096
|
-
if (remaining === 0) {
|
|
1097
|
-
resolve();
|
|
1098
|
-
}
|
|
1099
|
-
};
|
|
1100
|
-
for (const value of batch) {
|
|
1101
|
-
this._pending.push({ value, resolve: resolveOne, reject });
|
|
1102
|
-
}
|
|
1103
|
-
this._wakeWaiter();
|
|
1104
|
-
});
|
|
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();
|
|
1105
481
|
}
|
|
1106
482
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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);
|
|
1110
489
|
}
|
|
490
|
+
}
|
|
1111
491
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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");
|
|
1118
501
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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`);
|
|
1127
531
|
}
|
|
1128
532
|
}
|
|
1129
533
|
}
|
|
1130
534
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (this._closed) {
|
|
1145
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
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;
|
|
1146
548
|
}
|
|
549
|
+
this.#queue.push(payload);
|
|
550
|
+
this.#queuedBytes += payload.byteLength;
|
|
551
|
+
this.#notify();
|
|
552
|
+
};
|
|
1147
553
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
554
|
+
/** Ends the call out of order: receivers fail with `error` without draining the queue. */
|
|
555
|
+
#settleFailure(error) {
|
|
556
|
+
this.#failure ??= error;
|
|
557
|
+
this.#rejectOutcome(error);
|
|
558
|
+
this.#notify();
|
|
559
|
+
return this.#failure;
|
|
1151
560
|
}
|
|
1152
561
|
|
|
1153
|
-
|
|
1154
|
-
if (this
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
return Promise.reject(this._error);
|
|
562
|
+
#rejectOutcome(error) {
|
|
563
|
+
if (!this.#outcomeSettled) {
|
|
564
|
+
this.#outcomeSettled = true;
|
|
565
|
+
this.#rejectStatus(error);
|
|
566
|
+
this.#rejectHeaders(error);
|
|
1159
567
|
}
|
|
1160
|
-
if (this._closed) {
|
|
1161
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
return new Promise((resolve, reject) => {
|
|
1165
|
-
this._waiter = { resolve, reject, batch: true, max };
|
|
1166
|
-
});
|
|
1167
568
|
}
|
|
1168
569
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
570
|
+
/** Wakes everything waiting for a change of the response. */
|
|
571
|
+
#notify() {
|
|
572
|
+
const { resolve } = this.#changed;
|
|
573
|
+
this.#changed = deferred();
|
|
574
|
+
resolve();
|
|
1172
575
|
}
|
|
1173
576
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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;
|
|
1181
584
|
}
|
|
1182
|
-
return values;
|
|
1183
585
|
}
|
|
1184
586
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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;
|
|
1188
595
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (waiter.batch) {
|
|
1193
|
-
queueMicrotask(() => this._resolveBatchWaiter(waiter));
|
|
1194
|
-
return;
|
|
596
|
+
await this.#outcomeKnown();
|
|
597
|
+
if (this.#terminal?.status !== undefined) {
|
|
598
|
+
return this.#terminal.error ?? cancelled(EarlyOk);
|
|
1195
599
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
pending.resolve();
|
|
1199
|
-
waiter.resolve({ done: false, value: pending.value });
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
_resolveBatchWaiter(waiter) {
|
|
1203
|
-
if (this._pending.length > 0) {
|
|
1204
|
-
waiter.resolve({ done: false, value: this._takePendingBatch(waiter.max) });
|
|
1205
|
-
} else if (this._error != null) {
|
|
1206
|
-
waiter.reject(this._error);
|
|
1207
|
-
} else if (this._closed) {
|
|
1208
|
-
waiter.resolve({ done: true, value: undefined });
|
|
1209
|
-
} else {
|
|
1210
|
-
this._waiter = waiter;
|
|
600
|
+
if (this.#call.signal.aborted) {
|
|
601
|
+
return this.#call.signal.reason;
|
|
1211
602
|
}
|
|
603
|
+
return this.#terminal?.error ?? error;
|
|
1212
604
|
}
|
|
1213
605
|
}
|
|
1214
606
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
return {
|
|
1219
|
-
[Symbol.asyncIterator]() {
|
|
1220
|
-
return this;
|
|
1221
|
-
},
|
|
1222
|
-
async next() {
|
|
1223
|
-
if (done) {
|
|
1224
|
-
return { done: true, value: undefined };
|
|
1225
|
-
}
|
|
1226
|
-
const result = await nextRequest(iterator, signal);
|
|
1227
|
-
if (result.done) {
|
|
1228
|
-
done = true;
|
|
1229
|
-
return { done: true, value: undefined };
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
return { done: false, value: marshalMessage(requestType, result.value) };
|
|
1233
|
-
},
|
|
1234
|
-
async nextBatch(max = RequestStreamBatchSize) {
|
|
1235
|
-
if (done) {
|
|
1236
|
-
return { done: true, value: undefined };
|
|
1237
|
-
}
|
|
1238
|
-
const result = await nextRequestBatch(iterator, signal, max);
|
|
1239
|
-
if (result.done) {
|
|
1240
|
-
done = true;
|
|
1241
|
-
return { done: true, value: undefined };
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
return {
|
|
1245
|
-
done: false,
|
|
1246
|
-
value: result.value.map((value) => marshalMessage(requestType, value)),
|
|
1247
|
-
};
|
|
1248
|
-
},
|
|
1249
|
-
async return() {
|
|
1250
|
-
done = true;
|
|
1251
|
-
if (signal?.aborted && typeof iterator.return === "function") {
|
|
1252
|
-
await iterator.return();
|
|
1253
|
-
}
|
|
1254
|
-
return { done: true, value: undefined };
|
|
1255
|
-
},
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
607
|
+
/** A client-streaming RPC: send requests, then close and receive the single response. */
|
|
608
|
+
export class ClientStreamingCall {
|
|
609
|
+
#call;
|
|
1258
610
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
return requests[Symbol.asyncIterator]();
|
|
611
|
+
constructor(call) {
|
|
612
|
+
this.#call = call;
|
|
1262
613
|
}
|
|
1263
614
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
return
|
|
1267
|
-
next() {
|
|
1268
|
-
return Promise.resolve(iterator.next());
|
|
1269
|
-
},
|
|
1270
|
-
return() {
|
|
1271
|
-
return Promise.resolve(
|
|
1272
|
-
typeof iterator.return === "function"
|
|
1273
|
-
? iterator.return()
|
|
1274
|
-
: { done: true, value: undefined },
|
|
1275
|
-
);
|
|
1276
|
-
},
|
|
1277
|
-
};
|
|
615
|
+
/** Sends one request message. */
|
|
616
|
+
send(message) {
|
|
617
|
+
return this.#call.send(message);
|
|
1278
618
|
}
|
|
1279
619
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
function nextRequest(iterator, signal) {
|
|
1284
|
-
return nextRequestResult(() => iterator.next(), signal);
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
async function nextRequestBatch(iterator, signal, max) {
|
|
1288
|
-
if (typeof iterator.nextBatch === "function") {
|
|
1289
|
-
return await nextRequestResult(() => iterator.nextBatch(max), signal);
|
|
620
|
+
/** Sends every message of requests, then closes the request stream; see the bidirectional call. */
|
|
621
|
+
sendAll(requests) {
|
|
622
|
+
return this.#call.sendAll(requests);
|
|
1290
623
|
}
|
|
1291
624
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
function nextRequestResult(next, signal) {
|
|
1297
|
-
if (signal?.aborted) {
|
|
1298
|
-
return Promise.reject(signal.reason ?? cancelled("request stream cancelled"));
|
|
1299
|
-
}
|
|
1300
|
-
if (signal == null) {
|
|
1301
|
-
return next();
|
|
625
|
+
/** Closes the request stream and returns the final response. */
|
|
626
|
+
async closeAndRecv() {
|
|
627
|
+
this.#call.closeSend().catch(() => {});
|
|
628
|
+
return await receiveSingleResponse(this.#call, "client-streaming");
|
|
1302
629
|
}
|
|
1303
630
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
next().then(
|
|
1308
|
-
(value) => {
|
|
1309
|
-
signal.removeEventListener("abort", onAbort);
|
|
1310
|
-
resolve(value);
|
|
1311
|
-
},
|
|
1312
|
-
(error) => {
|
|
1313
|
-
signal.removeEventListener("abort", onAbort);
|
|
1314
|
-
reject(error);
|
|
1315
|
-
},
|
|
1316
|
-
);
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
async function* emptyAsyncIterable() {}
|
|
1321
|
-
|
|
1322
|
-
function copyDefined(target, source) {
|
|
1323
|
-
for (const [key, value] of Object.entries(source ?? {})) {
|
|
1324
|
-
if (value !== undefined && key !== "metadata") {
|
|
1325
|
-
target[key] = value;
|
|
1326
|
-
}
|
|
631
|
+
/** Cancels the call unless it already finished. */
|
|
632
|
+
close(reason) {
|
|
633
|
+
return this.#call.close(reason);
|
|
1327
634
|
}
|
|
1328
635
|
}
|
|
1329
636
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
637
|
+
/** The response stream of a server-streaming RPC. */
|
|
638
|
+
export class ResponseStream {
|
|
639
|
+
#call;
|
|
1333
640
|
|
|
1334
|
-
|
|
1335
|
-
|
|
641
|
+
constructor(call) {
|
|
642
|
+
this.#call = call;
|
|
1336
643
|
}
|
|
1337
644
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
: normalizeMetadata(base.metadata);
|
|
645
|
+
/** The final status after the stream ends, or the error that ended it. */
|
|
646
|
+
get status() {
|
|
647
|
+
return this.#call.status;
|
|
1342
648
|
}
|
|
1343
649
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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;
|
|
1348
656
|
}
|
|
1349
657
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
isPreparedCallOptions(override) ? override.metadata : normalizeMetadata(override.metadata),
|
|
1354
|
-
);
|
|
1355
|
-
validateMetadata(metadata);
|
|
1356
|
-
return metadata;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
function hasMetadataEntries(metadata) {
|
|
1360
|
-
if (metadata == null) {
|
|
1361
|
-
return false;
|
|
1362
|
-
}
|
|
1363
|
-
if (metadata instanceof Map) {
|
|
1364
|
-
return metadata.size > 0;
|
|
658
|
+
/** Cancels the call unless it already finished. */
|
|
659
|
+
close(reason) {
|
|
660
|
+
return this.#call.close(reason);
|
|
1365
661
|
}
|
|
1366
|
-
for (const _key in metadata) {
|
|
1367
|
-
return true;
|
|
1368
|
-
}
|
|
1369
|
-
return false;
|
|
1370
|
-
}
|
|
1371
662
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
if (key === "metadata") {
|
|
1375
|
-
if (hasMetadataEntries(value)) {
|
|
1376
|
-
return true;
|
|
1377
|
-
}
|
|
1378
|
-
} else if (value !== undefined) {
|
|
1379
|
-
return true;
|
|
1380
|
-
}
|
|
663
|
+
[Symbol.asyncIterator]() {
|
|
664
|
+
return this.#call[Symbol.asyncIterator]();
|
|
1381
665
|
}
|
|
1382
|
-
return false;
|
|
1383
666
|
}
|
|
1384
667
|
|
|
1385
|
-
function
|
|
1386
|
-
|
|
1387
|
-
|
|
668
|
+
function deferred() {
|
|
669
|
+
let resolve;
|
|
670
|
+
const promise = new Promise((resolvePromise) => {
|
|
671
|
+
resolve = resolvePromise;
|
|
672
|
+
});
|
|
673
|
+
return { promise, resolve };
|
|
1388
674
|
}
|
|
1389
675
|
|
|
1390
|
-
function
|
|
1391
|
-
return
|
|
676
|
+
function requestFields(rpc, kind) {
|
|
677
|
+
return { method: rpc.path, kind };
|
|
1392
678
|
}
|
|
1393
679
|
|
|
1394
|
-
function
|
|
1395
|
-
|
|
1396
|
-
|
|
680
|
+
function validateResponseMetadata(metadata, what) {
|
|
681
|
+
try {
|
|
682
|
+
validateMetadata(metadata);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
throw internal(`invalid response ${what}: ${error.statusMessage ?? error.message}`);
|
|
685
|
+
}
|
|
1397
686
|
}
|