@trevrpc/trevrpc-js 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -18
- package/bin/protoc-gen-trevrpc-js.js +1 -1
- package/package.json +17 -12
- package/src/call.js +616 -40
- package/src/channel.js +190 -18
- package/src/client.js +555 -106
- package/src/compression.js +330 -0
- package/src/connection-metadata.js +114 -0
- package/src/datagram.js +357 -0
- package/src/framing.js +124 -110
- package/src/generator.js +147 -16
- package/src/index.d.ts +223 -17
- package/src/qmux/frames.js +262 -0
- package/src/qmux/params.js +104 -0
- package/src/qmux/session.js +740 -0
- package/src/race.js +90 -0
- package/src/status.js +1 -1
- package/src/stream-compression.js +298 -0
- package/src/varint.js +73 -0
- package/src/wire.js +47 -28
- package/src/zstd.d.ts +35 -0
- package/src/zstd.js +407 -0
package/src/generator.js
CHANGED
|
@@ -62,6 +62,7 @@ message FieldDescriptorProto {
|
|
|
62
62
|
optional int32 oneof_index = 9;
|
|
63
63
|
optional string json_name = 10;
|
|
64
64
|
optional bool proto3_optional = 17;
|
|
65
|
+
optional FieldOptions options = 8;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
message OneofDescriptorProto {
|
|
@@ -89,6 +90,24 @@ message MethodDescriptorProto {
|
|
|
89
90
|
optional string output_type = 3;
|
|
90
91
|
optional bool client_streaming = 5 [default = false];
|
|
91
92
|
optional bool server_streaming = 6 [default = false];
|
|
93
|
+
optional MethodOptions options = 4;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Only the trevrpc.method extension is modelled; protobuf.js keeps every other extension out of
|
|
97
|
+
// the decoded message. The numbers are those of proto/trevrpc/options.proto.
|
|
98
|
+
message MethodOptions {
|
|
99
|
+
optional TrevrpcMethodOptions trevrpc_method = 58247;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
message TrevrpcMethodOptions {
|
|
103
|
+
optional bool unreliable_requests = 1;
|
|
104
|
+
optional bool unreliable_responses = 2;
|
|
105
|
+
// trevrpc.Compression, read as its number so that unknown values reach the check.
|
|
106
|
+
optional int32 compression = 5;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
message FieldOptions {
|
|
110
|
+
optional bool debug_redact = 16;
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
message FileOptions {
|
|
@@ -171,7 +190,6 @@ const GeneratedTypeImports = [
|
|
|
171
190
|
"BidirectionalStreamingCall",
|
|
172
191
|
"ClientStreamingCall",
|
|
173
192
|
"ResponseStream",
|
|
174
|
-
"UnaryResponse",
|
|
175
193
|
];
|
|
176
194
|
|
|
177
195
|
const RESERVED_WORDS = new Set([
|
|
@@ -225,6 +243,7 @@ export function generate(request) {
|
|
|
225
243
|
const protoFiles = request.protoFile ?? [];
|
|
226
244
|
const rootJson = buildProtobufJson(protoFiles);
|
|
227
245
|
const typeNames = declarationTypeNames(protoFiles);
|
|
246
|
+
const redacted = redactedTypes(protoFiles);
|
|
228
247
|
const files = [];
|
|
229
248
|
|
|
230
249
|
for (const file of protoFiles) {
|
|
@@ -235,11 +254,11 @@ export function generate(request) {
|
|
|
235
254
|
const fileName = outputFileName(file.name, options.fileSuffix);
|
|
236
255
|
files.push({
|
|
237
256
|
name: fileName,
|
|
238
|
-
content: generateFile(file, rootJson, options),
|
|
257
|
+
content: generateFile(file, rootJson, redacted, options),
|
|
239
258
|
});
|
|
240
259
|
files.push({
|
|
241
260
|
name: declarationFileName(fileName),
|
|
242
|
-
content: generateDeclarationFile(protoFiles, file, typeNames, options),
|
|
261
|
+
content: generateDeclarationFile(protoFiles, file, typeNames, redacted, options),
|
|
243
262
|
});
|
|
244
263
|
}
|
|
245
264
|
|
|
@@ -312,8 +331,10 @@ export function buildProtobufJson(files) {
|
|
|
312
331
|
return root;
|
|
313
332
|
}
|
|
314
333
|
|
|
315
|
-
function generateFile(file, rootJson, options) {
|
|
316
|
-
const services = (file.service ?? []).map((service) =>
|
|
334
|
+
function generateFile(file, rootJson, redacted, options) {
|
|
335
|
+
const services = (file.service ?? []).map((service) =>
|
|
336
|
+
serviceDescriptor(file, service, redacted),
|
|
337
|
+
);
|
|
317
338
|
const serviceExports = services.map((service) => generateService(service)).join("\n");
|
|
318
339
|
|
|
319
340
|
return `// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.\n\nimport { createRoot, createServiceClient } from ${JSON.stringify(
|
|
@@ -321,13 +342,13 @@ function generateFile(file, rootJson, options) {
|
|
|
321
342
|
)};\n\nexport const root = createRoot(${JSON.stringify(rootJson, null, 2)});\n\n${serviceExports}`;
|
|
322
343
|
}
|
|
323
344
|
|
|
324
|
-
function generateDeclarationFile(protoFiles, file, typeNames, options) {
|
|
345
|
+
function generateDeclarationFile(protoFiles, file, typeNames, redacted, options) {
|
|
325
346
|
const messages = collectMessages(protoFiles)
|
|
326
347
|
.filter(({ message }) => !message.options?.mapEntry)
|
|
327
348
|
.map((message) => generateMessageDeclaration(message, typeNames))
|
|
328
349
|
.join("\n\n");
|
|
329
350
|
const services = (file.service ?? [])
|
|
330
|
-
.map((service) => serviceDescriptor(file, service))
|
|
351
|
+
.map((service) => serviceDescriptor(file, service, redacted))
|
|
331
352
|
.map((service) => generateServiceDeclaration(service, typeNames))
|
|
332
353
|
.join("\n\n");
|
|
333
354
|
const sections = [messages, services].filter(Boolean).join("\n\n");
|
|
@@ -358,10 +379,6 @@ function generateServiceMethod(jsName, method) {
|
|
|
358
379
|
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(options = {}) {\n return this._client.${jsName}(options);\n }`;
|
|
359
380
|
}
|
|
360
381
|
|
|
361
|
-
if (method.kind === "unary") {
|
|
362
|
-
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request, options = {}) {\n return this._client.${jsName}(request, options);\n }\n\n /** Calls the ${method.name} RPC and returns response metadata. */\n ${jsName}WithResponse(request, options = {}) {\n return this._client.${jsName}WithResponse(request, options);\n }`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
382
|
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request, options = {}) {\n return this._client.${jsName}(request, options);\n }`;
|
|
366
383
|
}
|
|
367
384
|
|
|
@@ -381,7 +398,7 @@ function generateMethodDeclaration(jsName, method, typeNames) {
|
|
|
381
398
|
|
|
382
399
|
switch (method.kind) {
|
|
383
400
|
case "unary":
|
|
384
|
-
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request: ${inputType}, options?: CallOptions): Promise<${outputType}
|
|
401
|
+
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request: ${inputType}, options?: CallOptions): Promise<${outputType}>;`;
|
|
385
402
|
case "serverStreaming":
|
|
386
403
|
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request: ${inputType}, options?: CallOptions): Promise<ResponseStream<${outputType}>>;`;
|
|
387
404
|
case "clientStreaming":
|
|
@@ -393,7 +410,7 @@ function generateMethodDeclaration(jsName, method, typeNames) {
|
|
|
393
410
|
}
|
|
394
411
|
}
|
|
395
412
|
|
|
396
|
-
function serviceDescriptor(file, service) {
|
|
413
|
+
function serviceDescriptor(file, service, redacted) {
|
|
397
414
|
const fullName =
|
|
398
415
|
file.package === "" || file.package == null ? service.name : `${file.package}.${service.name}`;
|
|
399
416
|
const methods = {};
|
|
@@ -401,11 +418,18 @@ function serviceDescriptor(file, service) {
|
|
|
401
418
|
|
|
402
419
|
for (const method of service.method ?? []) {
|
|
403
420
|
const jsName = uniqueName(lowerCamelIdentifier(method.name), usedNames);
|
|
421
|
+
const kind = methodKind(method);
|
|
422
|
+
const inputType = stripLeadingDot(method.inputType);
|
|
423
|
+
const outputType = stripLeadingDot(method.outputType);
|
|
424
|
+
const methodName = `${fullName}.${method.name}`;
|
|
425
|
+
const options = method.options?.trevrpcMethod;
|
|
404
426
|
methods[jsName] = {
|
|
405
427
|
name: method.name,
|
|
406
|
-
kind
|
|
407
|
-
inputType
|
|
408
|
-
outputType
|
|
428
|
+
kind,
|
|
429
|
+
inputType,
|
|
430
|
+
outputType,
|
|
431
|
+
...unreliableDirections(methodName, kind, options),
|
|
432
|
+
...compressionDirections(methodName, kind, options, { inputType, outputType }, redacted),
|
|
409
433
|
};
|
|
410
434
|
}
|
|
411
435
|
|
|
@@ -417,6 +441,113 @@ function serviceDescriptor(file, service) {
|
|
|
417
441
|
};
|
|
418
442
|
}
|
|
419
443
|
|
|
444
|
+
/**
|
|
445
|
+
* The `(trevrpc.method)` flags of a method as descriptor fields, only for the ones that are set.
|
|
446
|
+
* A flag on a direction that does not stream is an error.
|
|
447
|
+
*/
|
|
448
|
+
function unreliableDirections(methodName, kind, options) {
|
|
449
|
+
const directions = {};
|
|
450
|
+
if (options?.unreliableRequests) {
|
|
451
|
+
if (kind !== "clientStreaming" && kind !== "bidirectionalStreaming") {
|
|
452
|
+
throw new Error(
|
|
453
|
+
`${methodName}: unreliable_requests requires a client-streaming or bidirectional method`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
directions.unreliableRequests = true;
|
|
457
|
+
}
|
|
458
|
+
if (options?.unreliableResponses) {
|
|
459
|
+
if (kind !== "serverStreaming" && kind !== "bidirectionalStreaming") {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`${methodName}: unreliable_responses requires a server-streaming or bidirectional method`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
directions.unreliableResponses = true;
|
|
465
|
+
}
|
|
466
|
+
return directions;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** `trevrpc.Compression` values, from proto/trevrpc/options.proto. */
|
|
470
|
+
const Compression = { unspecified: 0, message: 1, stream: 2 };
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The directions a method's `compression` lets compress, as descriptor fields, leaving out message
|
|
474
|
+
* types with a field marked `debug_redact`. `COMPRESSION_MESSAGE` compresses messages without a
|
|
475
|
+
* dictionary in both directions. `COMPRESSION_STREAM` does too, for the messages outside the
|
|
476
|
+
* stream, and stream-compresses the directions that stream; on a unary method it is an error, and
|
|
477
|
+
* so is a value this generator does not know.
|
|
478
|
+
*/
|
|
479
|
+
function compressionDirections(methodName, kind, options, { inputType, outputType }, redacted) {
|
|
480
|
+
const compression = options?.compression ?? Compression.unspecified;
|
|
481
|
+
if (compression === Compression.unspecified) {
|
|
482
|
+
return {};
|
|
483
|
+
}
|
|
484
|
+
if (compression !== Compression.message && compression !== Compression.stream) {
|
|
485
|
+
throw new Error(`${methodName}: unknown compression ${compression}`);
|
|
486
|
+
}
|
|
487
|
+
const directions = {};
|
|
488
|
+
if (!redacted.has(inputType)) {
|
|
489
|
+
directions.compressRequests = true;
|
|
490
|
+
}
|
|
491
|
+
if (!redacted.has(outputType)) {
|
|
492
|
+
directions.compressResponses = true;
|
|
493
|
+
}
|
|
494
|
+
if (compression === Compression.message) {
|
|
495
|
+
return directions;
|
|
496
|
+
}
|
|
497
|
+
if (kind === "unary") {
|
|
498
|
+
throw new Error(`${methodName}: COMPRESSION_STREAM requires a streaming method`);
|
|
499
|
+
}
|
|
500
|
+
if (
|
|
501
|
+
(kind === "clientStreaming" || kind === "bidirectionalStreaming") &&
|
|
502
|
+
!redacted.has(inputType)
|
|
503
|
+
) {
|
|
504
|
+
directions.compressStreamRequests = true;
|
|
505
|
+
}
|
|
506
|
+
if (
|
|
507
|
+
(kind === "serverStreaming" || kind === "bidirectionalStreaming") &&
|
|
508
|
+
!redacted.has(outputType)
|
|
509
|
+
) {
|
|
510
|
+
directions.compressStreamResponses = true;
|
|
511
|
+
}
|
|
512
|
+
return directions;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* The full names of the message types that contain a field marked `debug_redact`, directly or in
|
|
517
|
+
* a message-typed field, however deeply nested.
|
|
518
|
+
*/
|
|
519
|
+
function redactedTypes(files) {
|
|
520
|
+
const messages = new Map(
|
|
521
|
+
collectMessages(files).map(({ fullName, message }) => [fullName, message]),
|
|
522
|
+
);
|
|
523
|
+
const redacted = new Set();
|
|
524
|
+
const visiting = new Set();
|
|
525
|
+
const visit = (name) => {
|
|
526
|
+
if (redacted.has(name)) {
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
const message = messages.get(name);
|
|
530
|
+
if (message === undefined || visiting.has(name)) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
visiting.add(name);
|
|
534
|
+
const found = (message.field ?? []).some(
|
|
535
|
+
(field) =>
|
|
536
|
+
field.options?.debugRedact === true ||
|
|
537
|
+
(field.typeName != null && visit(stripLeadingDot(field.typeName))),
|
|
538
|
+
);
|
|
539
|
+
visiting.delete(name);
|
|
540
|
+
if (found) {
|
|
541
|
+
redacted.add(name);
|
|
542
|
+
}
|
|
543
|
+
return found;
|
|
544
|
+
};
|
|
545
|
+
for (const name of messages.keys()) {
|
|
546
|
+
visit(name);
|
|
547
|
+
}
|
|
548
|
+
return redacted;
|
|
549
|
+
}
|
|
550
|
+
|
|
420
551
|
function declarationTypeNames(files) {
|
|
421
552
|
const names = new Map();
|
|
422
553
|
const usedNames = new Set();
|
package/src/index.d.ts
CHANGED
|
@@ -52,6 +52,17 @@ export interface CallOptions {
|
|
|
52
52
|
metadata?: MetadataInput;
|
|
53
53
|
/** Cancels the call. */
|
|
54
54
|
signal?: AbortSignal;
|
|
55
|
+
/**
|
|
56
|
+
* Called with the response headers, empty when the server sent none, as soon as they, the first
|
|
57
|
+
* message or the final status arrive. A callback that throws fails the call.
|
|
58
|
+
*/
|
|
59
|
+
onResponseHeaders?(headers: Metadata): void;
|
|
60
|
+
/**
|
|
61
|
+
* Called with the trailing metadata of the final status when the call succeeds, before its
|
|
62
|
+
* result is returned or its response stream ends. A failed call reports its metadata on the
|
|
63
|
+
* TrevRpcError instead. A callback that throws fails the call.
|
|
64
|
+
*/
|
|
65
|
+
onResponseMetadata?(metadata: Metadata): void;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
export interface TransportStream {
|
|
@@ -59,9 +70,49 @@ export interface TransportStream {
|
|
|
59
70
|
writable: WritableStream<Uint8Array>;
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* The datagram side of a WebTransport session (`WebTransport.datagrams`). TrevRPC needs only
|
|
75
|
+
* `readable`; sending also needs `createWritable()` (or the older `writable`), and
|
|
76
|
+
* `maxDatagramSize` bounds what is sent as a datagram (1100 bytes if it is missing).
|
|
77
|
+
*/
|
|
78
|
+
export interface DatagramTransport {
|
|
79
|
+
readonly readable: ReadableStream<Uint8Array>;
|
|
80
|
+
readonly maxDatagramSize?: number;
|
|
81
|
+
createWritable?(): WritableStream<Uint8Array>;
|
|
82
|
+
readonly writable?: WritableStream<Uint8Array>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
declare const compressionBrand: unique symbol;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compression settings, made by `createCompression()` from `@trevrpc/trevrpc-js/zstd`: the zstd
|
|
89
|
+
* dictionaries a channel holds and the message types each compresses. See `ConnectOptions`.
|
|
90
|
+
*/
|
|
91
|
+
export interface Compression {
|
|
92
|
+
readonly [compressionBrand]: true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Anything that opens a bidirectional byte stream per call, such as a Channel or WebTransport.
|
|
97
|
+
* Calls to methods that mark a direction unreliable use the `datagrams` of the session that opens
|
|
98
|
+
* their stream, when it has them.
|
|
99
|
+
*/
|
|
63
100
|
export interface Transport {
|
|
64
101
|
createBidirectionalStream(): Promise<TransportStream>;
|
|
102
|
+
/** Datagrams of the session that the next stream opens on; absent without datagrams. */
|
|
103
|
+
readonly datagrams?: DatagramTransport;
|
|
104
|
+
/** Compression settings for calls on this transport; absent without compression. */
|
|
105
|
+
readonly compression?: Compression;
|
|
106
|
+
/**
|
|
107
|
+
* Like `createBidirectionalStream()`, but also returns the session that opened the stream.
|
|
108
|
+
* Calls negotiate dictionaries and open datagram flows per session, so transports whose session
|
|
109
|
+
* can change, such as a Channel, implement it so a call never mixes the stream of one session
|
|
110
|
+
* with the state of another.
|
|
111
|
+
*/
|
|
112
|
+
createBidirectionalStreamWithSession?(): Promise<{
|
|
113
|
+
stream: TransportStream;
|
|
114
|
+
session: { readonly datagrams?: DatagramTransport };
|
|
115
|
+
}>;
|
|
65
116
|
}
|
|
66
117
|
|
|
67
118
|
export type RpcMethodKind =
|
|
@@ -75,6 +126,35 @@ export interface RpcMethodDescriptor {
|
|
|
75
126
|
kind: RpcMethodKind;
|
|
76
127
|
inputType: string;
|
|
77
128
|
outputType: string;
|
|
129
|
+
/**
|
|
130
|
+
* The method marks its streamed request messages unreliable (`unreliable_requests` in the
|
|
131
|
+
* `trevrpc.method` option). They are sent as WebTransport datagrams when they fit and the
|
|
132
|
+
* session has datagrams, and may be lost or reordered; otherwise they travel on the stream.
|
|
133
|
+
*/
|
|
134
|
+
unreliableRequests?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* The method marks its streamed response messages unreliable (`unreliable_responses`). The
|
|
137
|
+
* client accepts them as datagrams on any session that has them.
|
|
138
|
+
*/
|
|
139
|
+
unreliableResponses?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* The method's request messages may be stream-compressed (`compression: COMPRESSION_STREAM` in
|
|
142
|
+
* the `trevrpc.method` option, on a method that streams requests, whose request type has no
|
|
143
|
+
* field marked `debug_redact`). Calls on a transport with compression then compress them with one zstd
|
|
144
|
+
* context per call, once the server agrees.
|
|
145
|
+
*/
|
|
146
|
+
compressStreamRequests?: boolean;
|
|
147
|
+
/** The method's response messages may be stream-compressed; see `compressStreamRequests`. */
|
|
148
|
+
compressStreamResponses?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The method's request messages may be compressed without a dictionary: one at a time, from 256
|
|
151
|
+
* bytes, where no dictionary applies to their type and the server decodes it. The generator
|
|
152
|
+
* sets it for `COMPRESSION_MESSAGE` and `COMPRESSION_STREAM`, except for message types with a
|
|
153
|
+
* field marked `debug_redact`.
|
|
154
|
+
*/
|
|
155
|
+
compressRequests?: boolean;
|
|
156
|
+
/** The method's response messages may be compressed without a dictionary; see `compressRequests`. */
|
|
157
|
+
compressResponses?: boolean;
|
|
78
158
|
}
|
|
79
159
|
|
|
80
160
|
export interface RpcServiceDescriptor {
|
|
@@ -84,11 +164,6 @@ export interface RpcServiceDescriptor {
|
|
|
84
164
|
methods: Record<string, RpcMethodDescriptor>;
|
|
85
165
|
}
|
|
86
166
|
|
|
87
|
-
export interface UnaryResponse<TResponse> {
|
|
88
|
-
readonly message: TResponse;
|
|
89
|
-
readonly metadata: Metadata;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
167
|
/** The final status of a call that ended with OK. */
|
|
93
168
|
export interface StreamStatus {
|
|
94
169
|
readonly code: StatusCode;
|
|
@@ -103,29 +178,94 @@ export interface StreamStatus {
|
|
|
103
178
|
export interface ResponseStream<TResponse> extends AsyncIterable<TResponse> {
|
|
104
179
|
/** Settles with the final status, or rejects with the error that ended the call. */
|
|
105
180
|
readonly status: Promise<StreamStatus>;
|
|
181
|
+
/**
|
|
182
|
+
* The response headers, empty when the server sent none. Settles when they, the first message or
|
|
183
|
+
* the final status arrive, before the first message is received, or rejects with the error that
|
|
184
|
+
* ended the call.
|
|
185
|
+
*/
|
|
186
|
+
readonly headers: Promise<Metadata>;
|
|
106
187
|
/** Cancels the call unless it already finished. */
|
|
107
188
|
close(reason?: unknown): Promise<void>;
|
|
108
189
|
}
|
|
109
190
|
|
|
110
191
|
export interface ClientStreamingCall<TRequest, TResponse> {
|
|
111
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Sends one request message; the promise settles when the transport accepted it, which applies
|
|
194
|
+
* backpressure.
|
|
195
|
+
*
|
|
196
|
+
* Rejects as soon as the server ends the call before the upload is done: with the server's
|
|
197
|
+
* `TrevRpcError` if its final status is not OK, even if the server never reads the rest of the
|
|
198
|
+
* request, and with `Code.Cancelled` if the status is OK (the call succeeded without the rest
|
|
199
|
+
* of the upload; `closeAndRecv()` returns its response). A response that fails after the
|
|
200
|
+
* upload stopped, such as a lost connection, is reported instead of a generic closed-stream
|
|
201
|
+
* error. A failed send is not retried or reported twice: later sends fail the same way, and
|
|
202
|
+
* `closeAndRecv()` returns the call's outcome.
|
|
203
|
+
*
|
|
204
|
+
* Pending sends are rejected too when the call is cancelled or its deadline expires.
|
|
205
|
+
*/
|
|
112
206
|
send(request: TRequest): Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Sends every message of `requests`, then closes the request stream. If the server ends the
|
|
209
|
+
* call first, the rest is not sent and the promise resolves; `closeAndRecv()` reports the
|
|
210
|
+
* outcome. A send that fails otherwise rejects as `send()` does, and requests that throw cancel
|
|
211
|
+
* the call and reject with what they threw.
|
|
212
|
+
*/
|
|
213
|
+
sendAll(requests: Iterable<TRequest> | AsyncIterable<TRequest>): Promise<void>;
|
|
113
214
|
/** Closes the request stream and returns the final response. */
|
|
114
215
|
closeAndRecv(): Promise<TResponse>;
|
|
115
|
-
/** Closes the request stream and returns the final response with its metadata. */
|
|
116
|
-
closeAndRecvWithResponse(): Promise<UnaryResponse<TResponse>>;
|
|
117
216
|
/** Cancels the call unless it already finished. */
|
|
118
217
|
close(reason?: unknown): Promise<void>;
|
|
119
218
|
}
|
|
120
219
|
|
|
220
|
+
/**
|
|
221
|
+
* On a method that marks a direction unreliable (`RpcMethodDescriptor.unreliableRequests` or
|
|
222
|
+
* `unreliableResponses`), a call on a session with datagrams may send its request messages, and
|
|
223
|
+
* receive its response messages, as datagrams: they can be lost or reordered, never duplicated.
|
|
224
|
+
* Response datagrams are delivered after the response headers, merged with the stream's messages
|
|
225
|
+
* in arrival order, and dropped when the call's queue is full (32 messages or 1 MiB) or the call
|
|
226
|
+
* has ended. Without datagrams, such as over WebSocket, everything stays on the stream.
|
|
227
|
+
*/
|
|
121
228
|
export interface BidirectionalStreamingCall<TRequest, TResponse> extends AsyncIterable<TResponse> {
|
|
122
229
|
/** Settles with the final status, or rejects with the error that ended the call. */
|
|
123
230
|
readonly status: Promise<StreamStatus>;
|
|
124
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* The response headers, empty when the server sent none. Settles when they, the first message or
|
|
233
|
+
* the final status arrive, before the first message is received, or rejects with the error that
|
|
234
|
+
* ended the call.
|
|
235
|
+
*/
|
|
236
|
+
readonly headers: Promise<Metadata>;
|
|
237
|
+
/**
|
|
238
|
+
* Sends one request message; the promise settles when the transport accepted it, which applies
|
|
239
|
+
* backpressure.
|
|
240
|
+
*
|
|
241
|
+
* Rejects as soon as the server ends the call before the upload is done, even if nobody is
|
|
242
|
+
* calling `recv()` and the server never reads the rest of the request: with the server's
|
|
243
|
+
* `TrevRpcError` if its final status is not OK, and with `Code.Cancelled` if the status is OK
|
|
244
|
+
* (`recv()` then returns `undefined` after the responses that were sent). A response that
|
|
245
|
+
* fails after the upload stopped, such as a lost connection, is reported instead of a generic
|
|
246
|
+
* closed-stream error. Later sends fail the same way, `closeSend()` resolves, and `recv()` and
|
|
247
|
+
* `status` report the call's outcome.
|
|
248
|
+
*
|
|
249
|
+
* While the request side is open the response is read ahead of `recv()` to find the final
|
|
250
|
+
* status: at most 32 frames or 1 MiB (plus one frame of at most `maxFrameSize`), after which
|
|
251
|
+
* reading pauses until `recv()` takes messages. Responses read ahead are delivered in order.
|
|
252
|
+
*
|
|
253
|
+
* Pending sends are rejected too when the call is cancelled or its deadline expires.
|
|
254
|
+
*/
|
|
125
255
|
send(request: TRequest): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Sends every message of `requests`, then closes the request stream. If the server ends the
|
|
258
|
+
* call first, the rest is not sent and the promise resolves; `recv()` and `status` report the
|
|
259
|
+
* outcome. A send that fails otherwise rejects as `send()` does, and requests that throw cancel
|
|
260
|
+
* the call and reject with what they threw.
|
|
261
|
+
*/
|
|
262
|
+
sendAll(requests: Iterable<TRequest> | AsyncIterable<TRequest>): Promise<void>;
|
|
126
263
|
/** Receives the next response message, or undefined after the final OK status. */
|
|
127
264
|
recv(): Promise<TResponse | undefined>;
|
|
128
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* Closes the request stream while keeping the response stream readable. Resolves if the server
|
|
267
|
+
* already ended the call; its final status is reported by `recv()` and `status`.
|
|
268
|
+
*/
|
|
129
269
|
closeSend(): Promise<void>;
|
|
130
270
|
/** Cancels the call unless it already finished. */
|
|
131
271
|
close(reason?: unknown): Promise<void>;
|
|
@@ -151,18 +291,65 @@ export interface WebTransportCertificateHash {
|
|
|
151
291
|
|
|
152
292
|
export interface WebTransportSessionLike extends Transport {
|
|
153
293
|
readonly ready: Promise<unknown>;
|
|
294
|
+
/**
|
|
295
|
+
* The WebTransport protocol the server selected. An empty or missing value means wire version 1.
|
|
296
|
+
* Any other value than "trevrpc-1" fails the connection attempt with FailedPrecondition.
|
|
297
|
+
*/
|
|
298
|
+
readonly protocol?: string;
|
|
154
299
|
readonly closed: Promise<unknown>;
|
|
155
300
|
close(closeInfo?: { closeCode?: number; reason?: string }): void;
|
|
156
301
|
}
|
|
157
302
|
|
|
303
|
+
/** How a channel reaches the server: WebTransport, or QMux over WebSocket as the fallback. */
|
|
304
|
+
export type TransportKind = "webtransport" | "websocket";
|
|
305
|
+
|
|
158
306
|
export interface ConnectOptions {
|
|
307
|
+
/**
|
|
308
|
+
* Transports to use, in order of preference. The default, `["webtransport", "websocket"]`,
|
|
309
|
+
* starts QMux over WebSocket once WebTransport fails or has not connected within
|
|
310
|
+
* `fallbackDelayMs`, and uses whichever is ready first.
|
|
311
|
+
*/
|
|
312
|
+
transports?: readonly TransportKind[];
|
|
313
|
+
/** The `wss://` URL of the server's TCP listener. Defaults to `url` with a `wss:` scheme. */
|
|
314
|
+
webSocketUrl?: string | URL;
|
|
315
|
+
/** How long WebTransport may take before the WebSocket fallback starts. Defaults to 300. */
|
|
316
|
+
fallbackDelayMs?: number;
|
|
317
|
+
/** How long reconnects keep using WebSocket after it won, before trying WebTransport again. */
|
|
318
|
+
reprobeIntervalMs?: number;
|
|
319
|
+
/** WebSocket implementation to use instead of the global one. */
|
|
320
|
+
WebSocket?: new (url: string | URL, protocols?: string | string[]) => WebSocket;
|
|
159
321
|
allowPooling?: boolean;
|
|
160
322
|
congestionControl?: "default" | "low-latency" | "throughput";
|
|
161
323
|
requireUnreliable?: boolean;
|
|
162
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Trusts a self-signed ECDSA certificate (at most 14 days old) by its SHA-256 hash. WebTransport
|
|
326
|
+
* only: the WebSocket fallback needs a certificate the browser trusts.
|
|
327
|
+
*/
|
|
163
328
|
serverCertificateHashes?: readonly WebTransportCertificateHash[];
|
|
164
|
-
/**
|
|
329
|
+
/**
|
|
330
|
+
* WebTransport implementation to use instead of the global one. It is constructed with
|
|
331
|
+
* `protocols: ["trevrpc-1"]`, the wire version, which cannot be changed.
|
|
332
|
+
*/
|
|
165
333
|
WebTransport?: new (url: string | URL, options?: object) => WebTransportSessionLike;
|
|
334
|
+
/**
|
|
335
|
+
* Compresses messages with the dictionaries these settings hold, from `createCompression()` in
|
|
336
|
+
* `@trevrpc/trevrpc-js/zstd`. Dictionaries are negotiated with the server per session, so a
|
|
337
|
+
* message whose dictionary the server lacks travels uncompressed. Compression settings also
|
|
338
|
+
* enable stream compression on methods that allow it, even without dictionaries.
|
|
339
|
+
*/
|
|
340
|
+
compression?: Compression;
|
|
341
|
+
/**
|
|
342
|
+
* The memory, in bytes, the stream compression contexts of this channel's calls may hold.
|
|
343
|
+
* Defaults to 64 MiB; 0 turns stream compression off. Calls beyond it compress per message.
|
|
344
|
+
*/
|
|
345
|
+
streamCompressionMemory?: number;
|
|
346
|
+
/**
|
|
347
|
+
* Request metadata for all of the channel's calls, such as a bearer token, which crosses each
|
|
348
|
+
* connection once instead of in every request. A call's own `metadata` wins over an entry with
|
|
349
|
+
* the same key. Keys are lowercase ASCII and the `trevrpc-` prefix is reserved, as for call
|
|
350
|
+
* metadata. Replace it with `Channel.setConnectionMetadata()`.
|
|
351
|
+
*/
|
|
352
|
+
connectionMetadata?: MetadataInput;
|
|
166
353
|
/** Cancels the initial connection attempt only. */
|
|
167
354
|
signal?: AbortSignal;
|
|
168
355
|
/** Receives channel state transitions. */
|
|
@@ -180,23 +367,42 @@ export interface ChannelStateChange {
|
|
|
180
367
|
delayMs?: number;
|
|
181
368
|
/** Why the session was lost or the previous attempt failed. */
|
|
182
369
|
error?: unknown;
|
|
370
|
+
/** The transport of the new session, when it becomes ready. */
|
|
371
|
+
transport?: TransportKind;
|
|
183
372
|
}
|
|
184
373
|
|
|
185
|
-
/** Opens a channel to a
|
|
374
|
+
/** Opens a channel to a TrevRPC server and waits for its session to be ready. */
|
|
186
375
|
export function connect(url: string | URL, options?: ConnectOptions): Promise<Channel>;
|
|
187
376
|
|
|
188
377
|
/**
|
|
189
|
-
* A
|
|
190
|
-
*
|
|
378
|
+
* A session that reconnects in the background after it is lost: WebTransport, or QMux over
|
|
379
|
+
* WebSocket when WebTransport is unavailable or blocked. Calls fail fast with Unavailable while it
|
|
380
|
+
* reconnects and are never replayed.
|
|
191
381
|
*/
|
|
192
382
|
export class Channel implements Transport {
|
|
193
383
|
private constructor();
|
|
194
384
|
|
|
195
|
-
/** Opens a channel to a
|
|
385
|
+
/** Opens a channel to a TrevRPC server and waits for its session to be ready. */
|
|
196
386
|
static connect(url: string | URL, options?: ConnectOptions): Promise<Channel>;
|
|
197
387
|
|
|
198
388
|
readonly state: ChannelState;
|
|
389
|
+
/** The transport of the current or last session. */
|
|
390
|
+
readonly transport: TransportKind;
|
|
391
|
+
/** The compression settings from the connect options. */
|
|
392
|
+
readonly compression?: Compression;
|
|
393
|
+
/**
|
|
394
|
+
* Replaces the channel's connection metadata (see `ConnectOptions.connectionMetadata`); empty or
|
|
395
|
+
* missing metadata removes it. Calls started after this use the new metadata, and each
|
|
396
|
+
* connection sends it again before referring to it. Throws if it is invalid, and then keeps the
|
|
397
|
+
* old.
|
|
398
|
+
*/
|
|
399
|
+
setConnectionMetadata(metadata?: MetadataInput): void;
|
|
199
400
|
createBidirectionalStream(): Promise<TransportStream>;
|
|
401
|
+
/** Opens a stream together with the session that carries it. */
|
|
402
|
+
createBidirectionalStreamWithSession(): Promise<{
|
|
403
|
+
stream: TransportStream;
|
|
404
|
+
session: { readonly datagrams?: DatagramTransport };
|
|
405
|
+
}>;
|
|
200
406
|
/** Resolves once the channel is ready, immediately if it already is. */
|
|
201
407
|
waitUntilReady(): Promise<void>;
|
|
202
408
|
/** Stops reconnecting and closes the current session. */
|