@trevrpc/trevrpc-js 0.2.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +89 -47
  2. package/bin/protoc-gen-trevrpc-js.js +1 -1
  3. package/package.json +11 -41
  4. package/src/bytes.js +10 -0
  5. package/src/call.js +287 -0
  6. package/src/channel.js +131 -296
  7. package/src/client.js +203 -1292
  8. package/src/framing.js +125 -519
  9. package/src/generator.js +26 -103
  10. package/src/index.d.ts +79 -452
  11. package/src/index.js +4 -3
  12. package/src/status.js +24 -70
  13. package/src/wire.js +4 -59
  14. package/examples/greeter/README.md +0 -35
  15. package/examples/greeter/client.js +0 -172
  16. package/examples/greeter/greeter.node.trevrpc.d.ts +0 -53
  17. package/examples/greeter/greeter.node.trevrpc.js +0 -9
  18. package/examples/greeter/greeter.proto +0 -18
  19. package/examples/greeter/greeter.trevrpc.d.ts +0 -55
  20. package/examples/greeter/greeter.trevrpc.js +0 -103
  21. package/examples/greeter/index.html +0 -140
  22. package/examples/greeter/static-server.js +0 -168
  23. package/src/advanced.d.ts +0 -29
  24. package/src/advanced.js +0 -1
  25. package/src/browser-channel.js +0 -49
  26. package/src/connect.js +0 -6
  27. package/src/connect.node.js +0 -6
  28. package/src/index.node.d.ts +0 -10
  29. package/src/index.node.js +0 -3
  30. package/src/native-loader.js +0 -118
  31. package/src/node-advanced.d.ts +0 -27
  32. package/src/node-advanced.js +0 -1
  33. package/src/node-generated.d.ts +0 -24
  34. package/src/node-generated.js +0 -126
  35. package/src/node-index.d.ts +0 -1
  36. package/src/node-index.js +0 -7
  37. package/src/node.d.ts +0 -298
  38. package/src/node.js +0 -1456
  39. package/src/runtime.js +0 -66
  40. package/src/server-response.js +0 -49
  41. package/src/timer.js +0 -29
  42. package/src/webtransport.js +0 -639
package/src/client.js CHANGED
@@ -1,467 +1,40 @@
1
- import { DefaultMaxFrameSize, marshalMessage, unmarshalMessage } from "./framing.js";
2
- import { normalizeMetadata, validateMetadata } from "./metadata.js";
3
- import {
4
- Code,
5
- FrameTooLargeError,
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 { marshalMessage, unmarshalMessage } from "./framing.js";
3
+ import { validateMetadata } from "./metadata.js";
4
+ import { Code, internal, statusError } from "./status.js";
5
+ import { RpcKind, RpcResponse, RpcStreamFrameKind } 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 requestType = root.lookupType(method.inputType);
406
- const responseType = root.lookupType(method.outputType);
407
- const callOptions = (override) => mergeCallOptions(baseOptions, override ?? {});
13
+ const rpc = {
14
+ service: service.fullName,
15
+ method: method.name,
16
+ requestType: root.lookupType(method.inputType),
17
+ responseType: root.lookupType(method.outputType),
18
+ };
19
+ const resolve = (override) => mergeCallOptions(defaults, override);
408
20
 
409
21
  switch (method.kind) {
410
22
  case "unary":
411
- client[jsName] = (request, override) =>
412
- unary(
413
- transport,
414
- service.fullName,
415
- method.name,
416
- requestType,
417
- responseType,
418
- request,
419
- callOptions(override),
420
- );
23
+ client[jsName] = async (request, override) =>
24
+ (await unaryCall(transport, rpc, request, resolve(override))).message;
421
25
  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
- );
26
+ unaryCall(transport, rpc, request, resolve(override));
431
27
  break;
432
28
  case "serverStreaming":
433
29
  client[jsName] = (request, override) =>
434
- serverStreaming(
435
- transport,
436
- service.fullName,
437
- method.name,
438
- requestType,
439
- responseType,
440
- request,
441
- callOptions(override),
442
- );
30
+ serverStreamingCall(transport, rpc, request, resolve(override));
443
31
  break;
444
32
  case "clientStreaming":
445
- client[jsName] = (override) =>
446
- clientStreaming(
447
- transport,
448
- service.fullName,
449
- method.name,
450
- requestType,
451
- responseType,
452
- callOptions(override),
453
- );
33
+ client[jsName] = (override) => clientStreamingCall(transport, rpc, resolve(override));
454
34
  break;
455
35
  case "bidirectionalStreaming":
456
36
  client[jsName] = (override) =>
457
- bidirectionalStreaming(
458
- transport,
459
- service.fullName,
460
- method.name,
461
- requestType,
462
- responseType,
463
- callOptions(override),
464
- );
37
+ bidirectionalStreamingCall(transport, rpc, resolve(override));
465
38
  break;
466
39
  default:
467
40
  throw internal(`unsupported generated RPC kind ${JSON.stringify(method.kind)}`);
@@ -471,927 +44,265 @@ export function createServiceClient(transport, service, root, options = {}) {
471
44
  return client;
472
45
  }
473
46
 
474
- function prepareClientRequest(service, method, kind, body, options) {
475
- const metadata = isPreparedCallOptions(options)
476
- ? (options.metadata ?? EmptyMetadata)
477
- : normalizeMetadata(options.metadata ?? {});
478
- const request = {
479
- service,
480
- method,
481
- body,
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;
496
- }
497
-
498
- function validateResponse(response, maxBodySize) {
499
- if (response == null) {
500
- throw internal("missing RPC response");
501
- }
502
-
503
- validateResponseMetadata(response.metadata ?? {});
504
-
505
- const body = response.body ?? new Uint8Array(0);
506
- if (body.byteLength > maxBodySize) {
507
- throw new FrameTooLargeError(body.byteLength, maxBodySize);
508
- }
509
-
510
- return statusFromResponse(response);
511
- }
512
-
513
- function responseMessageStream(
514
- frameStream,
515
- responseType,
516
- options,
517
- abortScope,
518
- onTerminal = () => {},
519
- ) {
520
- return new ResponseStreamController(frameStream, responseType, options, abortScope, onTerminal);
521
- }
522
-
523
- class ResponseStreamController {
524
- constructor(frameStream, responseType, options, abortScope, onTerminal) {
525
- this._iterator = frameStream[Symbol.asyncIterator]();
526
- this._responseType = responseType;
527
- this._options = options;
528
- this._abortScope = abortScope;
529
- this._onTerminal = onTerminal;
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
- });
544
- this.status.catch(() => {});
545
- this._onAbort = () => {
546
- const error = signalAbortError(this._options.signal);
547
- this._recordFailure(error);
548
- void this._cleanup(error);
549
- };
550
- if (this._options.signal?.aborted) {
551
- this._onAbort();
552
- } else {
553
- this._options.signal?.addEventListener?.("abort", this._onAbort, { once: true });
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;
574
- }
575
- const error = cancellationReason(reason, "response stream cancelled");
576
- this._abortScope?.abort(error);
577
- this._recordFailure(error);
578
- await this._cleanup(error);
579
- }
580
-
581
- _serialize(operation) {
582
- const result = this._operation.then(operation, operation);
583
- this._operation = result.catch(() => {});
584
- return result;
585
- }
586
-
587
- async _next() {
588
- if (this._failure != null) {
589
- throw this._failure;
590
- }
591
- if (this._complete) {
592
- return { done: true, value: undefined };
593
- }
594
-
595
- try {
596
- for (;;) {
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
- }
624
- } catch (error) {
625
- const primary = this._recordFailure(error);
626
- await this._cleanup(primary);
627
- throw primary;
628
- }
629
- }
630
-
631
- _consumeBatch(value) {
632
- if (this._useBodyBatches) {
633
- for (const body of value.bodies) {
634
- validateResponseMessageBody(body, this._options, this._counters);
635
- this._messages.push(unmarshalMessage(this._responseType, body));
636
- }
637
- if (value.status != null) {
638
- this._pendingStatus = this._claimStatus(value.status);
639
- }
640
- return;
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;
671
- }
672
- const error = invalidArgument("response stream contained an unknown frame kind");
673
- error.reason = "unsupported_frame_kind";
674
- throw error;
675
- }
676
- }
677
-
678
- _claimStatus(frame) {
679
- validateResponseMetadata(frame.metadata ?? {});
680
- const status = {
681
- code: frame.status ?? Code.Ok,
682
- message: frame.message ?? "",
683
- metadata: frame.metadata ?? {},
684
- };
685
- const terminalError =
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;
704
- try {
705
- end = await this._readNext();
706
- } catch (error) {
707
- if (!isMalformedTrailingRead(error)) {
708
- throw error;
709
- }
710
- const trailing = new StreamStateError(
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;
728
- }
729
- this._complete = true;
730
- this._settleStatus(true, status);
731
- }
732
-
733
- _readNext() {
734
- return this._useBodyBatches
735
- ? nextBodyBatchWithTimeout(
736
- this._iterator,
737
- this._options.streamIdleTimeoutMs,
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
- );
747
- }
748
-
749
- _recordFailure(error) {
750
- if (this._failure == null && !this._complete) {
751
- this._failure = error;
752
- this._settleStatus(false, error);
753
- }
754
- return this._failure ?? error;
755
- }
756
-
757
- _settleStatus(success, value) {
758
- if (this._statusSettled) {
759
- return;
760
- }
761
- this._statusSettled = true;
762
- if (success) {
763
- this._resolveStatus(value);
764
- } else {
765
- this._rejectStatus(value);
766
- }
767
- }
768
-
769
- _cleanup(primaryError) {
770
- if (this._cleanupPromise != null) {
771
- return this._cleanupPromise;
772
- }
773
- this._cleanupPromise = (async () => {
774
- this._options.signal?.removeEventListener?.("abort", this._onAbort);
775
- try {
776
- if (typeof this._iterator.return === "function") {
777
- await this._iterator.return();
778
- }
779
- } catch (error) {
780
- if (primaryError == null) {
781
- throw error;
782
- }
783
- } finally {
784
- this._abortScope?.cleanup();
785
- }
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
- }
47
+ /** Calls a unary RPC and returns the decoded response with its metadata. */
48
+ export async function unaryCall(transport, rpc, request, options) {
49
+ const body = marshalMessage(rpc.requestType, request);
50
+ const call = await Call.open(transport, requestHeader(rpc, RpcKind.Unary, body), options);
797
51
 
798
- if (body.byteLength > options.maxResponseBodySize) {
799
- throw new FrameTooLargeError(body.byteLength, options.maxResponseBodySize);
52
+ let response;
53
+ try {
54
+ // The response decides the outcome, so the request side finishes in the background.
55
+ call.closeSend().catch(() => {});
56
+ response = await call.readFrame(RpcResponse);
57
+ validateResponseMetadata(response.metadata);
58
+ } catch (error) {
59
+ throw call.fail(error);
800
60
  }
61
+ call.finish();
801
62
 
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
- );
63
+ if (response.status !== Code.Ok) {
64
+ throw statusError(response.status, response.message, response.metadata);
811
65
  }
66
+ return {
67
+ message: unmarshalMessage(rpc.responseType, response.body),
68
+ metadata: response.metadata,
69
+ };
812
70
  }
813
71
 
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,
72
+ /** Calls a server-streaming RPC and returns its response stream. */
73
+ export async function serverStreamingCall(transport, rpc, request, options) {
74
+ const body = marshalMessage(rpc.requestType, request);
75
+ const call = await Call.open(
76
+ transport,
77
+ requestHeader(rpc, RpcKind.ServerStreaming, body),
78
+ options,
821
79
  );
80
+ const stream = new BidirectionalStreamingCall(call, rpc);
81
+ stream.closeSend().catch(() => {});
82
+ return new ResponseStream(stream);
822
83
  }
823
84
 
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,
85
+ /** Calls a client-streaming RPC and returns a call for sending requests. */
86
+ export async function clientStreamingCall(transport, rpc, options) {
87
+ const call = await Call.open(
88
+ transport,
89
+ requestHeader(rpc, RpcKind.ClientStreaming, new Uint8Array(0)),
90
+ options,
832
91
  );
833
- return hasBatchReader || result.done ? result : { done: false, value: [result.value] };
92
+ return new ClientStreamingCall(new BidirectionalStreamingCall(call, rpc));
834
93
  }
835
94
 
836
- function isMalformedTrailingRead(error) {
837
- return (
838
- error instanceof FrameTooLargeError ||
839
- error?.reason === "malformed_protobuf" ||
840
- error?.reason === "incomplete_frame"
95
+ /** Calls a bidirectional-streaming RPC. */
96
+ export async function bidirectionalStreamingCall(transport, rpc, options) {
97
+ const call = await Call.open(
98
+ transport,
99
+ requestHeader(rpc, RpcKind.BidirectionalStreaming, new Uint8Array(0)),
100
+ options,
841
101
  );
102
+ return new BidirectionalStreamingCall(call, rpc);
842
103
  }
843
104
 
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
- }
105
+ /**
106
+ * A bidirectional-streaming RPC. Messages are received with recv() or async iteration. The call
107
+ * 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.
109
+ */
110
+ export class BidirectionalStreamingCall {
111
+ #call;
112
+ #rpc;
113
+ /** Serializes recv() so concurrent callers never interleave frame reads. */
114
+ #reads = Promise.resolve();
115
+ #settled = false;
116
+ #error = null;
117
+ #uploadError = null;
118
+ #resolveStatus;
119
+ #rejectStatus;
120
+
121
+ /** The final status, or the error that ended the call. */
122
+ status = new Promise((resolve, reject) => {
123
+ this.#resolveStatus = resolve;
124
+ this.#rejectStatus = reject;
125
+ });
858
126
 
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;
127
+ constructor(call, rpc) {
128
+ this.#call = call;
129
+ this.#rpc = rpc;
130
+ this.status.catch(() => {});
131
+ call.signal.addEventListener("abort", () => this.#settleFailure(call.signal.reason), {
132
+ once: true,
133
+ });
134
+ if (call.signal.aborted) {
135
+ this.#settleFailure(call.signal.reason);
865
136
  }
866
- messageCount += 1;
867
- }
868
-
869
- const status = stream.status == null ? { metadata: {} } : await stream.status;
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
137
  }
882
138
 
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");
139
+ /** Sends one request message. */
140
+ async send(message) {
141
+ const body = marshalMessage(this.#rpc.requestType, message);
142
+ await this.#recordUploadError(this.#call.sendMessage(body));
893
143
  }
894
144
 
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;
145
+ /** Closes the request stream while keeping the response stream readable. */
146
+ async closeSend() {
147
+ await this.#recordUploadError(this.#call.closeSend());
902
148
  }
903
149
 
904
- if (timeoutMs != null && !Number.isFinite(timeoutMs)) {
905
- throw invalidArgument("RPC timeout must be a finite number of milliseconds");
150
+ /** Receives the next response message, or undefined after the final OK status. */
151
+ recv() {
152
+ const next = this.#reads.then(() => this.#recv());
153
+ this.#reads = next.catch(() => {});
154
+ return next;
906
155
  }
907
156
 
908
- return new Promise((resolve, reject) => {
909
- let settled = false;
910
- let cancelTimer = () => {};
911
- const cleanup = () => {
912
- cancelTimer();
913
- signal?.removeEventListener?.("abort", onAbort);
914
- };
915
- const settle = (complete, value) => {
916
- if (settled) {
917
- return;
918
- }
919
- settled = true;
920
- cleanup();
921
- complete(value);
922
- };
923
- const onAbort = () => settle(reject, signalAbortError(signal));
924
-
925
- if (timeoutMs != null && timeoutMs >= 0) {
926
- cancelTimer = scheduleTimeout(() => {
927
- const error = makeError();
928
- onTimeout?.(error);
929
- settle(reject, error);
930
- }, timeoutMs);
931
- }
932
-
933
- if (signal != null) {
934
- if (signal.aborted) {
935
- onAbort();
936
- return;
937
- }
938
- signal.addEventListener("abort", onAbort, { once: true });
157
+ /** Cancels the call unless it already finished. */
158
+ async close(reason) {
159
+ if (!this.#settled) {
160
+ this.#call.cancel(reason);
939
161
  }
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
162
  }
965
- return error;
966
- }
967
163
 
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) {
164
+ [Symbol.asyncIterator]() {
985
165
  return {
986
- options,
987
- signal: undefined,
988
- abort() {
989
- return false;
166
+ next: async () => {
167
+ const value = await this.recv();
168
+ return value === undefined ? { done: true, value: undefined } : { done: false, value };
990
169
  },
991
- terminal() {
992
- return true;
170
+ return: async () => {
171
+ await this.close();
172
+ return { done: true, value: undefined };
993
173
  },
994
- cleanup() {},
995
174
  };
996
175
  }
997
176
 
998
- if (options.timeoutMs != null && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
999
- throw invalidArgument("RPC timeout must be a non-negative finite number of milliseconds");
1000
- }
1001
-
1002
- const controller = new AbortController();
1003
- const parentSignal = options.signal;
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") {
1017
- return false;
1018
- }
1019
- settlement = "aborted";
1020
- clearDeadline();
1021
- removeParent();
1022
- controller.abort(normalizeAbortReason(reason));
1023
- return true;
1024
- };
1025
- const terminal = () => {
1026
- if (settlement !== "active") {
1027
- return false;
177
+ async #recv() {
178
+ if (this.#error != null) {
179
+ throw this.#error;
1028
180
  }
1029
- settlement = "terminal";
1030
- clearDeadline();
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 });
181
+ if (this.#settled) {
182
+ return undefined;
1041
183
  }
1042
- }
1043
- if (deadlineError != null && settlement === "active") {
1044
- cancelDeadline = scheduleTimeout(() => abort(deadlineError), options.timeoutMs);
1045
- }
1046
184
 
1047
- return {
1048
- options: markPreparedCallOptions({ ...options, signal: controller.signal }),
1049
- signal: controller.signal,
1050
- abort,
1051
- terminal,
1052
- cleanup() {
1053
- if (cleaned) {
1054
- return;
185
+ let frame;
186
+ try {
187
+ frame = await this.#call.readStreamFrame();
188
+ if (frame === null) {
189
+ throw this.#uploadError ?? internal("response stream ended before final status");
1055
190
  }
1056
- cleaned = true;
1057
- clearDeadline();
1058
- removeParent();
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();
1073
- } else {
1074
- signal?.addEventListener?.("abort", this._onAbort, { once: true });
1075
- }
1076
- }
1077
-
1078
- send(value) {
1079
- return this.sendMany([value]);
1080
- }
1081
-
1082
- sendMany(values) {
1083
- if (this._closed) {
1084
- return Promise.reject(this._error ?? cancelled("request stream is closed"));
1085
- }
1086
-
1087
- const batch = Array.from(values);
1088
- if (batch.length === 0) {
1089
- return Promise.resolve();
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 });
191
+ if (frame.kind === RpcStreamFrameKind.Message) {
192
+ return unmarshalMessage(this.#rpc.responseType, frame.body);
1102
193
  }
1103
- this._wakeWaiter();
1104
- });
1105
- }
1106
-
1107
- close(error) {
1108
- if (this._closed) {
1109
- return;
194
+ validateResponseMetadata(frame.metadata);
195
+ } catch (error) {
196
+ throw this.#settleFailure(this.#call.fail(error));
1110
197
  }
1111
198
 
1112
- this._closed = true;
1113
- this._error = error;
1114
- this._signal?.removeEventListener?.("abort", this._onAbort);
1115
- if (error != null) {
1116
- for (const pending of this._pending.splice(0)) {
1117
- pending.reject(error);
1118
- }
1119
- }
1120
- if (this._waiter != null) {
1121
- const waiter = this._waiter;
1122
- this._waiter = undefined;
1123
- if (error == null) {
1124
- waiter.resolve({ done: true, value: undefined });
1125
- } else {
1126
- waiter.reject(error);
1127
- }
199
+ // A status frame is final: the request side stops and the stream drains in the background.
200
+ this.#call.finish();
201
+ this.#settled = true;
202
+ if (frame.status !== Code.Ok) {
203
+ this.#error = statusError(frame.status, frame.message, frame.metadata);
204
+ this.#rejectStatus(this.#error);
205
+ throw this.#error;
1128
206
  }
207
+ this.#resolveStatus({ code: Code.Ok, message: frame.message, metadata: frame.metadata });
208
+ return undefined;
1129
209
  }
1130
210
 
1131
- [Symbol.asyncIterator]() {
1132
- return this;
1133
- }
1134
-
1135
- next() {
1136
- if (this._pending.length > 0) {
1137
- const pending = this._pending.shift();
1138
- pending.resolve();
1139
- return Promise.resolve({ done: false, value: pending.value });
211
+ #settleFailure(error) {
212
+ if (!this.#settled) {
213
+ this.#settled = true;
214
+ this.#error = error;
215
+ this.#rejectStatus(error);
1140
216
  }
1141
- if (this._error != null) {
1142
- return Promise.reject(this._error);
1143
- }
1144
- if (this._closed) {
1145
- return Promise.resolve({ done: true, value: undefined });
1146
- }
1147
-
1148
- return new Promise((resolve, reject) => {
1149
- this._waiter = { resolve, reject };
1150
- });
217
+ return this.#error;
1151
218
  }
1152
219
 
1153
- nextBatch(max = RequestStreamBatchSize) {
1154
- if (this._pending.length > 0) {
1155
- return Promise.resolve({ done: false, value: this._takePendingBatch(max) });
1156
- }
1157
- if (this._error != null) {
1158
- return Promise.reject(this._error);
1159
- }
1160
- if (this._closed) {
1161
- return Promise.resolve({ done: true, value: undefined });
220
+ async #recordUploadError(upload) {
221
+ try {
222
+ await upload;
223
+ } catch (error) {
224
+ this.#uploadError ??= error;
225
+ throw error;
1162
226
  }
1163
-
1164
- return new Promise((resolve, reject) => {
1165
- this._waiter = { resolve, reject, batch: true, max };
1166
- });
1167
227
  }
228
+ }
1168
229
 
1169
- return() {
1170
- this.close(cancelled("request stream cancelled"));
1171
- return Promise.resolve({ done: true, value: undefined });
1172
- }
230
+ /** A client-streaming RPC: send requests, then close and receive the single response. */
231
+ export class ClientStreamingCall {
232
+ #call;
1173
233
 
1174
- _takePendingBatch(max) {
1175
- const count = Math.min(Math.max(1, max), this._pending.length);
1176
- const values = Array.from({ length: count });
1177
- for (let i = 0; i < count; i++) {
1178
- const pending = this._pending.shift();
1179
- pending.resolve();
1180
- values[i] = pending.value;
1181
- }
1182
- return values;
234
+ constructor(call) {
235
+ this.#call = call;
1183
236
  }
1184
237
 
1185
- _wakeWaiter() {
1186
- if (this._waiter == null || this._pending.length === 0) {
1187
- return;
1188
- }
1189
-
1190
- const waiter = this._waiter;
1191
- this._waiter = undefined;
1192
- if (waiter.batch) {
1193
- queueMicrotask(() => this._resolveBatchWaiter(waiter));
1194
- return;
1195
- }
1196
-
1197
- const pending = this._pending.shift();
1198
- pending.resolve();
1199
- waiter.resolve({ done: false, value: pending.value });
238
+ /** Sends one request message. */
239
+ send(message) {
240
+ return this.#call.send(message);
1200
241
  }
1201
242
 
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;
1211
- }
243
+ /** Closes the request stream and returns the final response. */
244
+ async closeAndRecv() {
245
+ return (await this.closeAndRecvWithResponse()).message;
1212
246
  }
1213
- }
1214
247
 
1215
- function encodeRequestStream(requestType, requests, signal) {
1216
- const iterator = requestIterator(requests);
1217
- let done = false;
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
- }
248
+ /** Closes the request stream and returns the final response with its metadata. */
249
+ async closeAndRecvWithResponse() {
250
+ this.#call.closeSend().catch(() => {});
1243
251
 
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();
252
+ let message;
253
+ let count = 0;
254
+ for (;;) {
255
+ const next = await this.#call.recv();
256
+ if (next === undefined) {
257
+ break;
1253
258
  }
1254
- return { done: true, value: undefined };
1255
- },
1256
- };
1257
- }
1258
-
1259
- function requestIterator(requests) {
1260
- if (typeof requests?.[Symbol.asyncIterator] === "function") {
1261
- return requests[Symbol.asyncIterator]();
1262
- }
1263
-
1264
- if (typeof requests?.[Symbol.iterator] === "function") {
1265
- const iterator = requests[Symbol.iterator]();
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
- };
1278
- }
1279
-
1280
- throw invalidArgument("request stream must be an async iterable or iterable");
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);
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 };
1290
267
  }
1291
268
 
1292
- const result = await nextRequest(iterator, signal);
1293
- return result.done ? result : { done: false, value: [result.value] };
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();
269
+ /** Cancels the call unless it already finished. */
270
+ close(reason) {
271
+ return this.#call.close(reason);
1302
272
  }
1303
-
1304
- return new Promise((resolve, reject) => {
1305
- const onAbort = () => reject(signal.reason ?? cancelled("request stream cancelled"));
1306
- signal.addEventListener("abort", onAbort, { once: true });
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
273
  }
1319
274
 
1320
- async function* emptyAsyncIterable() {}
275
+ /** The response stream of a server-streaming RPC. */
276
+ export class ResponseStream {
277
+ #call;
1321
278
 
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
- }
279
+ constructor(call) {
280
+ this.#call = call;
1327
281
  }
1328
- }
1329
-
1330
- function mergeMetadata(base, override) {
1331
- const baseHasMetadata = hasMetadataEntries(base?.metadata);
1332
- const overrideHasMetadata = hasMetadataEntries(override?.metadata);
1333
282
 
1334
- if (!baseHasMetadata && !overrideHasMetadata) {
1335
- return EmptyMetadata;
283
+ /** The final status after the stream ends, or the error that ended it. */
284
+ get status() {
285
+ return this.#call.status;
1336
286
  }
1337
287
 
1338
- if (!overrideHasMetadata) {
1339
- return isPreparedCallOptions(base)
1340
- ? (base.metadata ?? EmptyMetadata)
1341
- : normalizeMetadata(base.metadata);
288
+ /** Cancels the call unless it already finished. */
289
+ close(reason) {
290
+ return this.#call.close(reason);
1342
291
  }
1343
292
 
1344
- if (!baseHasMetadata) {
1345
- return isPreparedCallOptions(override)
1346
- ? (override.metadata ?? EmptyMetadata)
1347
- : normalizeMetadata(override.metadata);
293
+ [Symbol.asyncIterator]() {
294
+ return this.#call[Symbol.asyncIterator]();
1348
295
  }
1349
-
1350
- const metadata = Object.assign(
1351
- Object.create(null),
1352
- isPreparedCallOptions(base) ? base.metadata : normalizeMetadata(base.metadata),
1353
- isPreparedCallOptions(override) ? override.metadata : normalizeMetadata(override.metadata),
1354
- );
1355
- validateMetadata(metadata);
1356
- return metadata;
1357
296
  }
1358
297
 
1359
- function hasMetadataEntries(metadata) {
1360
- if (metadata == null) {
1361
- return false;
1362
- }
1363
- if (metadata instanceof Map) {
1364
- return metadata.size > 0;
1365
- }
1366
- for (const _key in metadata) {
1367
- return true;
1368
- }
1369
- return false;
298
+ function requestHeader(rpc, kind, body) {
299
+ return { service: rpc.service, method: rpc.method, kind, body };
1370
300
  }
1371
301
 
1372
- function hasDefinedOptions(options) {
1373
- for (const [key, value] of Object.entries(options ?? {})) {
1374
- if (key === "metadata") {
1375
- if (hasMetadataEntries(value)) {
1376
- return true;
1377
- }
1378
- } else if (value !== undefined) {
1379
- return true;
1380
- }
302
+ function validateResponseMetadata(metadata) {
303
+ try {
304
+ validateMetadata(metadata);
305
+ } catch (error) {
306
+ throw internal(`invalid response metadata: ${error.statusMessage ?? error.message}`);
1381
307
  }
1382
- return false;
1383
- }
1384
-
1385
- function markPreparedCallOptions(options) {
1386
- Object.defineProperty(options, PreparedCallOptions, { value: true });
1387
- return options;
1388
- }
1389
-
1390
- function isPreparedCallOptions(options) {
1391
- return options?.[PreparedCallOptions] === true;
1392
- }
1393
-
1394
- function saturatingAdd(left, right) {
1395
- const sum = left + right;
1396
- return Number.isSafeInteger(sum) ? sum : Number.MAX_SAFE_INTEGER;
1397
308
  }