@trevrpc/trevrpc-js 1.3.1 → 1.4.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 +67 -9
- package/package.json +2 -2
- package/src/certificate-hashes.js +57 -0
- package/src/channel.js +172 -54
- package/src/client.js +70 -22
- package/src/connection-metadata.js +5 -5
- package/src/generator.js +275 -68
- package/src/index.d.ts +280 -10
- package/src/index.js +15 -1
- package/src/int64.js +288 -0
- package/src/interceptor.js +102 -0
- package/src/stream-compression.js +5 -5
- package/src/wire.js +31 -7
- package/src/wkt.js +137 -0
package/src/index.d.ts
CHANGED
|
@@ -2,8 +2,138 @@ import type { INamespace, Root } from "protobufjs";
|
|
|
2
2
|
|
|
3
3
|
export const protobuf: typeof import("protobufjs");
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* How decoded messages represent 64-bit integer fields: `bigint`, `number` (exact up to 2^53),
|
|
7
|
+
* decimal `string`, or protobuf.js's `long` (a Long object). 64-bit map keys are decimal strings
|
|
8
|
+
* in every representation.
|
|
9
|
+
*/
|
|
10
|
+
export type Int64Representation = "bigint" | "number" | "string" | "long";
|
|
11
|
+
|
|
12
|
+
export interface CreateRootOptions {
|
|
13
|
+
/**
|
|
14
|
+
* How calls decode the 64-bit integers of the root's messages; a field's `jstype` option
|
|
15
|
+
* (`JS_STRING` or `JS_NUMBER`) overrides it. Without it they stay Long objects. Generated code
|
|
16
|
+
* passes the generator's `int64` option.
|
|
17
|
+
*/
|
|
18
|
+
int64?: Int64Representation;
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
/** Creates a protobuf root from a JSON namespace definition. */
|
|
6
|
-
export function createRoot(json: INamespace): Root;
|
|
22
|
+
export function createRoot(json: INamespace, options?: CreateRootOptions): Root;
|
|
23
|
+
|
|
24
|
+
/** A 64-bit integer in any representation, including a Long. */
|
|
25
|
+
type Int64Value = bigint | number | string | { low: number; high: number; unsigned?: boolean };
|
|
26
|
+
|
|
27
|
+
/** A decoded google.protobuf.Timestamp message. */
|
|
28
|
+
export interface Timestamp {
|
|
29
|
+
seconds: bigint;
|
|
30
|
+
nanos: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A google.protobuf.Timestamp message to send. */
|
|
34
|
+
export interface TimestampInit {
|
|
35
|
+
seconds?: bigint | number | string;
|
|
36
|
+
nanos?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A decoded google.protobuf.Duration message. */
|
|
40
|
+
export interface Duration {
|
|
41
|
+
seconds: bigint;
|
|
42
|
+
nanos: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A google.protobuf.Duration message to send. */
|
|
46
|
+
export interface DurationInit {
|
|
47
|
+
seconds?: bigint | number | string;
|
|
48
|
+
nanos?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A decoded google.protobuf.Empty message. */
|
|
52
|
+
export interface Empty {}
|
|
53
|
+
|
|
54
|
+
/** A google.protobuf.Empty message to send. */
|
|
55
|
+
export interface EmptyInit {}
|
|
56
|
+
|
|
57
|
+
/** Values of the google.protobuf.NullValue enum. */
|
|
58
|
+
export const NullValue: Readonly<{ NULL_VALUE: 0 }>;
|
|
59
|
+
/** A value of the google.protobuf.NullValue enum. */
|
|
60
|
+
export type NullValue = (typeof NullValue)[keyof typeof NullValue];
|
|
61
|
+
|
|
62
|
+
/** A decoded google.protobuf.Struct message: a JSON object; see `structToJson`. */
|
|
63
|
+
export interface Struct {
|
|
64
|
+
fields: Record<string, Value>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A google.protobuf.Struct message to send; see `structFromJson`. */
|
|
68
|
+
export interface StructInit {
|
|
69
|
+
fields?: Record<string, ValueInit>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A decoded google.protobuf.Value message: a JSON value, which sets one member of the `kind`
|
|
74
|
+
* oneof; see `valueToJson`.
|
|
75
|
+
*/
|
|
76
|
+
export interface Value {
|
|
77
|
+
nullValue: NullValue;
|
|
78
|
+
numberValue: number;
|
|
79
|
+
stringValue: string;
|
|
80
|
+
boolValue: boolean;
|
|
81
|
+
structValue: Struct | null;
|
|
82
|
+
listValue: ListValue | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A google.protobuf.Value message to send; see `valueFromJson`. */
|
|
86
|
+
export interface ValueInit {
|
|
87
|
+
nullValue?: NullValue;
|
|
88
|
+
numberValue?: number;
|
|
89
|
+
stringValue?: string;
|
|
90
|
+
boolValue?: boolean;
|
|
91
|
+
structValue?: StructInit | null;
|
|
92
|
+
listValue?: ListValueInit | null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A decoded google.protobuf.ListValue message: a JSON array. */
|
|
96
|
+
export interface ListValue {
|
|
97
|
+
values: Value[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A google.protobuf.ListValue message to send. */
|
|
101
|
+
export interface ListValueInit {
|
|
102
|
+
values?: ValueInit[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
106
|
+
export type JsonObject = { [key: string]: JsonValue };
|
|
107
|
+
|
|
108
|
+
/** Returns the Timestamp of a Date, to the millisecond. */
|
|
109
|
+
export function timestampFromDate(date: Date): Timestamp;
|
|
110
|
+
/** Returns the Timestamp of milliseconds since the Unix epoch. */
|
|
111
|
+
export function timestampFromMs(ms: number): Timestamp;
|
|
112
|
+
/** Returns the Date of a Timestamp, whatever the representation of its seconds. */
|
|
113
|
+
export function timestampDate(timestamp: {
|
|
114
|
+
seconds?: Int64Value | null;
|
|
115
|
+
nanos?: number | null;
|
|
116
|
+
}): Date;
|
|
117
|
+
/** Returns the milliseconds since the Unix epoch of a Timestamp, rounded down. */
|
|
118
|
+
export function timestampMs(timestamp: {
|
|
119
|
+
seconds?: Int64Value | null;
|
|
120
|
+
nanos?: number | null;
|
|
121
|
+
}): number;
|
|
122
|
+
/** Returns the Duration of a number of milliseconds. */
|
|
123
|
+
export function durationFromMs(ms: number): Duration;
|
|
124
|
+
/** Returns the milliseconds of a Duration, rounded towards zero. */
|
|
125
|
+
export function durationMs(duration: {
|
|
126
|
+
seconds?: Int64Value | null;
|
|
127
|
+
nanos?: number | null;
|
|
128
|
+
}): number;
|
|
129
|
+
/** Returns the Struct of a JSON object. */
|
|
130
|
+
export function structFromJson(json: JsonObject): StructInit;
|
|
131
|
+
/** Returns the JSON object of a Struct. */
|
|
132
|
+
export function structToJson(struct: StructInit): JsonObject;
|
|
133
|
+
/** Returns the Value of a JSON value. */
|
|
134
|
+
export function valueFromJson(json: JsonValue): ValueInit;
|
|
135
|
+
/** Returns the JSON value of a Value; one with no member of `kind` set is null. */
|
|
136
|
+
export function valueToJson(value: ValueInit): JsonValue;
|
|
7
137
|
|
|
8
138
|
export const Code: Readonly<{
|
|
9
139
|
Ok: 0;
|
|
@@ -65,6 +195,59 @@ export interface CallOptions {
|
|
|
65
195
|
onResponseMetadata?(metadata: Metadata): void;
|
|
66
196
|
}
|
|
67
197
|
|
|
198
|
+
/** The kind of an RPC, as in its generated descriptor. */
|
|
199
|
+
export type CallKind = "unary" | "serverStreaming" | "clientStreaming" | "bidirectionalStreaming";
|
|
200
|
+
|
|
201
|
+
/** What a call interceptor sees of a call before it starts. */
|
|
202
|
+
export interface InterceptedCall {
|
|
203
|
+
/** The method's full path, such as `/example.greeter.Greeter/SayHello`. */
|
|
204
|
+
readonly method: string;
|
|
205
|
+
/** The service's full name, such as `example.greeter.Greeter`. */
|
|
206
|
+
readonly service: string;
|
|
207
|
+
/** The method's name, such as `SayHello`. */
|
|
208
|
+
readonly name: string;
|
|
209
|
+
readonly kind: CallKind;
|
|
210
|
+
/**
|
|
211
|
+
* The call's request metadata. An interceptor may add, replace and delete entries, such as a
|
|
212
|
+
* refreshed token; the result is validated after the last interceptor, and invalid metadata
|
|
213
|
+
* fails the call with InvalidArgument.
|
|
214
|
+
*/
|
|
215
|
+
metadata: Record<string, MetadataValue>;
|
|
216
|
+
/** The call's deadline, which starts once the interceptors are done. */
|
|
217
|
+
readonly timeoutMs: number | undefined;
|
|
218
|
+
/** The call's abort signal, which an interceptor that waits can watch. */
|
|
219
|
+
readonly signal: AbortSignal | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** How an intercepted call ended. */
|
|
223
|
+
export interface CallEnd {
|
|
224
|
+
/** `Code.Ok` when the call succeeded; the code of `error` otherwise, or `Code.Unknown`. */
|
|
225
|
+
readonly code: StatusCode;
|
|
226
|
+
/** What failed the call, usually a TrevRpcError; undefined when it succeeded. */
|
|
227
|
+
readonly error: unknown;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Called once when an intercepted call ends. A finish function that throws is reported, not rethrown. */
|
|
231
|
+
export type CallFinish = (end: CallEnd) => void;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Runs before each call of a client, in order with the others. It may change the call's metadata,
|
|
235
|
+
* wait (for example for a token refresh), and return a function that is called once when the call
|
|
236
|
+
* ends: when a unary call settles, and when a streaming call receives its final status or fails,
|
|
237
|
+
* including when it is cancelled. Finish functions run innermost first. A streaming call that is
|
|
238
|
+
* neither read to its end nor closed does not end. An interceptor that throws fails the call with
|
|
239
|
+
* what it threw, before it starts; the finish functions of earlier interceptors still run.
|
|
240
|
+
*/
|
|
241
|
+
export type CallInterceptor = (
|
|
242
|
+
call: InterceptedCall,
|
|
243
|
+
) => void | CallFinish | Promise<void | CallFinish>;
|
|
244
|
+
|
|
245
|
+
/** Options of a service client: defaults for its calls, and interceptors that run around them. */
|
|
246
|
+
export interface ClientOptions extends CallOptions {
|
|
247
|
+
/** Interceptors for the client's calls, after those of its transport, such as a Channel's. */
|
|
248
|
+
interceptors?: readonly CallInterceptor[];
|
|
249
|
+
}
|
|
250
|
+
|
|
68
251
|
export interface TransportStream {
|
|
69
252
|
readable: ReadableStream<Uint8Array>;
|
|
70
253
|
writable: WritableStream<Uint8Array>;
|
|
@@ -83,6 +266,8 @@ export interface DatagramTransport {
|
|
|
83
266
|
}
|
|
84
267
|
|
|
85
268
|
declare const compressionBrand: unique symbol;
|
|
269
|
+
declare const connectionMetadataBrand: unique symbol;
|
|
270
|
+
declare const streamCompressionBudgetBrand: unique symbol;
|
|
86
271
|
|
|
87
272
|
/**
|
|
88
273
|
* Compression settings, made by `createCompression()` from `@trevrpc/trevrpc-js/zstd`: the zstd
|
|
@@ -92,6 +277,22 @@ export interface Compression {
|
|
|
92
277
|
readonly [compressionBrand]: true;
|
|
93
278
|
}
|
|
94
279
|
|
|
280
|
+
/**
|
|
281
|
+
* A Channel's connection metadata, which `Channel.setConnectionMetadata()` replaces. See
|
|
282
|
+
* `Transport.connectionMetadata`.
|
|
283
|
+
*/
|
|
284
|
+
export interface ConnectionMetadataSource {
|
|
285
|
+
readonly [connectionMetadataBrand]: true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* A Channel's stream compression memory budget, from `ConnectOptions.streamCompressionMemory`.
|
|
290
|
+
* See `Transport.streamCompressionBudget`.
|
|
291
|
+
*/
|
|
292
|
+
export interface StreamCompressionBudget {
|
|
293
|
+
readonly [streamCompressionBudgetBrand]: true;
|
|
294
|
+
}
|
|
295
|
+
|
|
95
296
|
/**
|
|
96
297
|
* Anything that opens a bidirectional byte stream per call, such as a Channel or WebTransport.
|
|
97
298
|
* Calls to methods that mark a direction unreliable use the `datagrams` of the session that opens
|
|
@@ -103,6 +304,22 @@ export interface Transport {
|
|
|
103
304
|
readonly datagrams?: DatagramTransport;
|
|
104
305
|
/** Compression settings for calls on this transport; absent without compression. */
|
|
105
306
|
readonly compression?: Compression;
|
|
307
|
+
/**
|
|
308
|
+
* The connection metadata calls on this transport carry; a Channel's own. A transport that
|
|
309
|
+
* wraps a Channel forwards it, or its calls carry no connection metadata.
|
|
310
|
+
*/
|
|
311
|
+
readonly connectionMetadata?: ConnectionMetadataSource;
|
|
312
|
+
/**
|
|
313
|
+
* The memory budget of the stream compression contexts of calls on this transport; a Channel's
|
|
314
|
+
* own. A transport that wraps a Channel forwards it, or its calls get a default budget of their
|
|
315
|
+
* own.
|
|
316
|
+
*/
|
|
317
|
+
readonly streamCompressionBudget?: StreamCompressionBudget;
|
|
318
|
+
/**
|
|
319
|
+
* Interceptors that clients created on this transport run around every call, before their own;
|
|
320
|
+
* a Channel's own. A transport that wraps a Channel forwards them, or its clients skip them.
|
|
321
|
+
*/
|
|
322
|
+
readonly interceptors?: readonly CallInterceptor[];
|
|
106
323
|
/**
|
|
107
324
|
* Like `createBidirectionalStream()`, but also returns the session that opened the stream.
|
|
108
325
|
* Calls negotiate dictionaries and open datagram flows per session, so transports whose session
|
|
@@ -211,6 +428,8 @@ export interface ClientStreamingCall<TRequest, TResponse> {
|
|
|
211
428
|
* the call and reject with what they threw.
|
|
212
429
|
*/
|
|
213
430
|
sendAll(requests: Iterable<TRequest> | AsyncIterable<TRequest>): Promise<void>;
|
|
431
|
+
/** Settles with the final status, or rejects with the error that ended the call. */
|
|
432
|
+
readonly status: Promise<StreamStatus>;
|
|
214
433
|
/** Closes the request stream and returns the final response. */
|
|
215
434
|
closeAndRecv(): Promise<TResponse>;
|
|
216
435
|
/** Cancels the call unless it already finished. */
|
|
@@ -276,12 +495,15 @@ export type ServiceClient = Record<
|
|
|
276
495
|
(requestOrOptions?: unknown, options?: CallOptions) => Promise<unknown>
|
|
277
496
|
>;
|
|
278
497
|
|
|
279
|
-
/**
|
|
498
|
+
/**
|
|
499
|
+
* Creates a service client from a generated service descriptor. Its calls run the transport's
|
|
500
|
+
* interceptors, then those of `options.interceptors`.
|
|
501
|
+
*/
|
|
280
502
|
export function createServiceClient<TClient extends ServiceClient = ServiceClient>(
|
|
281
503
|
transport: Transport,
|
|
282
504
|
service: RpcServiceDescriptor,
|
|
283
505
|
root: Root,
|
|
284
|
-
options?:
|
|
506
|
+
options?: ClientOptions,
|
|
285
507
|
): TClient;
|
|
286
508
|
|
|
287
509
|
export interface WebTransportCertificateHash {
|
|
@@ -289,6 +511,22 @@ export interface WebTransportCertificateHash {
|
|
|
289
511
|
value: ArrayBuffer | ArrayBufferView;
|
|
290
512
|
}
|
|
291
513
|
|
|
514
|
+
export interface FetchServerCertificateHashesOptions {
|
|
515
|
+
signal?: AbortSignal;
|
|
516
|
+
/** The fetch implementation to use instead of the global one. */
|
|
517
|
+
fetch?: (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Fetches the certificate hashes a TrevRPC development server publishes, as JSON such as
|
|
522
|
+
* `[{"algorithm":"sha-256","value":"<hex>"}]`, for `serverCertificateHashes`. A 404 yields no
|
|
523
|
+
* hashes; any other failure rejects with Unavailable.
|
|
524
|
+
*/
|
|
525
|
+
export function fetchServerCertificateHashes(
|
|
526
|
+
url: string | URL,
|
|
527
|
+
options?: FetchServerCertificateHashesOptions,
|
|
528
|
+
): Promise<WebTransportCertificateHash[]>;
|
|
529
|
+
|
|
292
530
|
export interface WebTransportSessionLike extends Transport {
|
|
293
531
|
readonly ready: Promise<unknown>;
|
|
294
532
|
/**
|
|
@@ -326,6 +564,14 @@ export interface ConnectOptions {
|
|
|
326
564
|
* only: the WebSocket fallback needs a certificate the browser trusts.
|
|
327
565
|
*/
|
|
328
566
|
serverCertificateHashes?: readonly WebTransportCertificateHash[];
|
|
567
|
+
/**
|
|
568
|
+
* A URL that publishes the server's development certificate hashes, such as a path that the
|
|
569
|
+
* development web server proxies to the TrevRPC server's DevelopmentCertificate. Every
|
|
570
|
+
* WebTransport attempt fetches it, so reconnects follow a rotated certificate, and adds its
|
|
571
|
+
* hashes to `serverCertificateHashes`; a 404 adds none, so the same build works against a server
|
|
572
|
+
* with a trusted certificate. A failed fetch fails the WebTransport attempt.
|
|
573
|
+
*/
|
|
574
|
+
serverCertificateHashesUrl?: string | URL;
|
|
329
575
|
/**
|
|
330
576
|
* WebTransport implementation to use instead of the global one. It is constructed with
|
|
331
577
|
* `protocols: ["trevrpc-1"]`, the wire version, which cannot be changed.
|
|
@@ -350,13 +596,18 @@ export interface ConnectOptions {
|
|
|
350
596
|
* metadata. Replace it with `Channel.setConnectionMetadata()`.
|
|
351
597
|
*/
|
|
352
598
|
connectionMetadata?: MetadataInput;
|
|
353
|
-
/**
|
|
599
|
+
/**
|
|
600
|
+
* Interceptors that every client created on the channel runs around its calls, before the
|
|
601
|
+
* client's own; see `CallInterceptor`.
|
|
602
|
+
*/
|
|
603
|
+
interceptors?: readonly CallInterceptor[];
|
|
604
|
+
/** Cancels the initial connection attempt of `connect()` only; `createChannel()` ignores it. */
|
|
354
605
|
signal?: AbortSignal;
|
|
355
606
|
/** Receives channel state transitions. */
|
|
356
607
|
onStateChange?(change: ChannelStateChange): void;
|
|
357
608
|
}
|
|
358
609
|
|
|
359
|
-
export type ChannelState = "ready" | "reconnecting" | "closed";
|
|
610
|
+
export type ChannelState = "connecting" | "ready" | "reconnecting" | "closed";
|
|
360
611
|
|
|
361
612
|
export interface ChannelStateChange {
|
|
362
613
|
state: ChannelState;
|
|
@@ -371,13 +622,26 @@ export interface ChannelStateChange {
|
|
|
371
622
|
transport?: TransportKind;
|
|
372
623
|
}
|
|
373
624
|
|
|
374
|
-
/**
|
|
625
|
+
/**
|
|
626
|
+
* Opens a channel to a TrevRPC server and waits for its session to be ready. It rejects, leaving
|
|
627
|
+
* nothing behind, if the first attempt fails.
|
|
628
|
+
*/
|
|
375
629
|
export function connect(url: string | URL, options?: ConnectOptions): Promise<Channel>;
|
|
376
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Returns a channel to a TrevRPC server that connects in the background, so that clients can be
|
|
633
|
+
* created on it right away. Calls started before its first session is ready wait for that attempt
|
|
634
|
+
* (within their own deadline and signal); a failed attempt fails them, and the channel goes on
|
|
635
|
+
* reconnecting with backoff. Close the channel to give up; the `signal` option does not apply.
|
|
636
|
+
* Throws if an option is invalid.
|
|
637
|
+
*/
|
|
638
|
+
export function createChannel(url: string | URL, options?: ConnectOptions): Channel;
|
|
639
|
+
|
|
377
640
|
/**
|
|
378
641
|
* A session that reconnects in the background after it is lost: WebTransport, or QMux over
|
|
379
|
-
* WebSocket when WebTransport is unavailable or blocked. Calls
|
|
380
|
-
* reconnects and are never
|
|
642
|
+
* WebSocket when WebTransport is unavailable or blocked. Calls started while it first connects wait
|
|
643
|
+
* for that attempt; afterwards they fail fast with Unavailable while it reconnects and are never
|
|
644
|
+
* replayed.
|
|
381
645
|
*/
|
|
382
646
|
export class Channel implements Transport {
|
|
383
647
|
private constructor();
|
|
@@ -386,10 +650,16 @@ export class Channel implements Transport {
|
|
|
386
650
|
static connect(url: string | URL, options?: ConnectOptions): Promise<Channel>;
|
|
387
651
|
|
|
388
652
|
readonly state: ChannelState;
|
|
389
|
-
/** The transport of the current or last session. */
|
|
390
|
-
readonly transport: TransportKind;
|
|
653
|
+
/** The transport of the current or last session; undefined before the first. */
|
|
654
|
+
readonly transport: TransportKind | undefined;
|
|
391
655
|
/** The compression settings from the connect options. */
|
|
392
656
|
readonly compression?: Compression;
|
|
657
|
+
/** The connection metadata of the channel's calls, which a wrapping Transport forwards. */
|
|
658
|
+
readonly connectionMetadata: ConnectionMetadataSource;
|
|
659
|
+
/** The stream compression memory budget of the channel's calls, which a wrapping Transport forwards. */
|
|
660
|
+
readonly streamCompressionBudget: StreamCompressionBudget;
|
|
661
|
+
/** The interceptors from the connect options, which a wrapping Transport forwards. */
|
|
662
|
+
readonly interceptors: readonly CallInterceptor[];
|
|
393
663
|
/**
|
|
394
664
|
* Replaces the channel's connection metadata (see `ConnectOptions.connectionMetadata`); empty or
|
|
395
665
|
* missing metadata removes it. Calls started after this use the new metadata, and each
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { fetchServerCertificateHashes } from "./certificate-hashes.js";
|
|
2
|
+
export { Channel, connect, createChannel } from "./channel.js";
|
|
2
3
|
export { createServiceClient } from "./client.js";
|
|
3
4
|
export { Code, TrevRpcError, codeName } from "./status.js";
|
|
4
5
|
export { createRoot, protobuf } from "./wire.js";
|
|
6
|
+
export {
|
|
7
|
+
NullValue,
|
|
8
|
+
durationFromMs,
|
|
9
|
+
durationMs,
|
|
10
|
+
structFromJson,
|
|
11
|
+
structToJson,
|
|
12
|
+
timestampDate,
|
|
13
|
+
timestampFromDate,
|
|
14
|
+
timestampFromMs,
|
|
15
|
+
timestampMs,
|
|
16
|
+
valueFromJson,
|
|
17
|
+
valueToJson,
|
|
18
|
+
} from "./wkt.js";
|
package/src/int64.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import protobuf from "protobufjs";
|
|
2
|
+
|
|
3
|
+
import { invalidArgument } from "./status.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How decoded 64-bit integer fields are represented: `bigint`, `number` (exact up to 2^53),
|
|
7
|
+
* decimal `string`, or protobuf.js's `long` (a Long object, as protobuf.js decodes them).
|
|
8
|
+
*/
|
|
9
|
+
export const Int64Representations = Object.freeze(["bigint", "number", "string", "long"]);
|
|
10
|
+
|
|
11
|
+
/** The representation of each root's 64-bit fields, for roots made with createRoot(). */
|
|
12
|
+
const rootRepresentations = new WeakMap();
|
|
13
|
+
/** The conversion plan of each message type, or null when its messages need none. */
|
|
14
|
+
const plans = new WeakMap();
|
|
15
|
+
|
|
16
|
+
const Int64Types = new Set(["int64", "uint64", "sint64", "fixed64", "sfixed64"]);
|
|
17
|
+
const UnsignedTypes = new Set(["uint64", "fixed64"]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sets how the decoded messages of `root`'s types represent 64-bit integers. Without it they keep
|
|
21
|
+
* protobuf.js's Long objects. A field's `jstype` option (`JS_STRING` or `JS_NUMBER`) overrides it.
|
|
22
|
+
*/
|
|
23
|
+
export function setInt64Representation(root, representation) {
|
|
24
|
+
if (!Int64Representations.includes(representation)) {
|
|
25
|
+
throw invalidArgument(
|
|
26
|
+
`int64 must be one of ${Int64Representations.join(", ")}, not ${JSON.stringify(representation)}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
rootRepresentations.set(root, representation);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns a function that converts, in place, the 64-bit integers of a message of `type` that
|
|
34
|
+
* protobuf.js decoded, as its root's representation says, and returns the message; or undefined
|
|
35
|
+
* when its messages need no conversion. 64-bit map keys, which protobuf.js decodes as 8-byte hash
|
|
36
|
+
* strings, become decimal strings in every representation.
|
|
37
|
+
*/
|
|
38
|
+
export function int64Converter(type) {
|
|
39
|
+
const plan = planFor(type);
|
|
40
|
+
return plan === null ? undefined : (message) => applyPlan(plan, message);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts a 64-bit integer, as a bigint, a number, a decimal string, or a Long-like object with
|
|
45
|
+
* `low`, `high` and `unsigned`, to a bigint. Null and undefined are zero.
|
|
46
|
+
*/
|
|
47
|
+
export function int64ToBigInt(value, unsigned = value?.unsigned === true) {
|
|
48
|
+
switch (typeof value) {
|
|
49
|
+
case "bigint":
|
|
50
|
+
return value;
|
|
51
|
+
case "number":
|
|
52
|
+
return BigInt(Math.trunc(value));
|
|
53
|
+
case "string":
|
|
54
|
+
return BigInt(value);
|
|
55
|
+
default: {
|
|
56
|
+
if (value == null) {
|
|
57
|
+
return 0n;
|
|
58
|
+
}
|
|
59
|
+
const bits = (BigInt(value.high >>> 0) << 32n) | BigInt(value.low >>> 0);
|
|
60
|
+
return unsigned ? bits : BigInt.asIntN(64, bits);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function planFor(type) {
|
|
66
|
+
const cached = plans.get(type);
|
|
67
|
+
if (cached !== undefined) {
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
const representation = rootRepresentations.get(type.root);
|
|
71
|
+
if (representation === undefined) {
|
|
72
|
+
plans.set(type, null);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
buildPlans(type, representation);
|
|
76
|
+
return plans.get(type);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Builds the plans of `start` and every message type reachable from it. A type needs a plan when
|
|
81
|
+
* it has a 64-bit field to convert, or a message field whose type needs one; that is settled for
|
|
82
|
+
* every reachable type at once, so recursive types are handled.
|
|
83
|
+
*/
|
|
84
|
+
function buildPlans(start, representation) {
|
|
85
|
+
const steps = new Map();
|
|
86
|
+
const pending = [start];
|
|
87
|
+
steps.set(start, undefined);
|
|
88
|
+
while (pending.length > 0) {
|
|
89
|
+
const type = pending.pop();
|
|
90
|
+
const typeSteps = [];
|
|
91
|
+
for (const field of type.fieldsArray) {
|
|
92
|
+
field.resolve();
|
|
93
|
+
const step = fieldStep(type, field, representation);
|
|
94
|
+
if (step !== undefined) {
|
|
95
|
+
typeSteps.push(step);
|
|
96
|
+
}
|
|
97
|
+
if (field.resolvedType instanceof protobuf.Type && !steps.has(field.resolvedType)) {
|
|
98
|
+
steps.set(field.resolvedType, undefined);
|
|
99
|
+
pending.push(field.resolvedType);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
steps.set(type, typeSteps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A type needs a plan when it converts a value itself or holds a type that needs one.
|
|
106
|
+
const needed = new Set();
|
|
107
|
+
for (let changed = true; changed;) {
|
|
108
|
+
changed = false;
|
|
109
|
+
for (const [type, typeSteps] of steps) {
|
|
110
|
+
if (
|
|
111
|
+
!needed.has(type) &&
|
|
112
|
+
typeSteps.some((step) => convertsItself(step) || needed.has(step.type))
|
|
113
|
+
) {
|
|
114
|
+
needed.add(type);
|
|
115
|
+
changed = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const built = new Map();
|
|
121
|
+
for (const type of steps.keys()) {
|
|
122
|
+
if (!plans.has(type)) {
|
|
123
|
+
built.set(type, needed.has(type) ? [] : null);
|
|
124
|
+
restoreNullDefaults(type);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const [type, plan] of built) {
|
|
128
|
+
plans.set(type, plan);
|
|
129
|
+
}
|
|
130
|
+
for (const [type, plan] of built) {
|
|
131
|
+
if (plan === null) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
for (const step of steps.get(type)) {
|
|
135
|
+
const nested = step.type === undefined ? undefined : plans.get(step.type);
|
|
136
|
+
if (!convertsItself(step) && nested == null) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
plan.push({ ...step, nested: nested ?? undefined });
|
|
140
|
+
}
|
|
141
|
+
patchDefaults(type, plan);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Reports whether a step converts values or keys of its own, not only nested messages. */
|
|
146
|
+
function convertsItself(step) {
|
|
147
|
+
return step.convert !== undefined || step.convertKey !== undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The conversion step of one field of `type`, or undefined when it holds neither a 64-bit integer
|
|
152
|
+
* nor a message. `convert` converts its 64-bit values, `convertKey` its 64-bit map keys, and
|
|
153
|
+
* `type` is the message type of its values.
|
|
154
|
+
*/
|
|
155
|
+
function fieldStep(type, field, representation) {
|
|
156
|
+
const messageType = field.resolvedType instanceof protobuf.Type ? field.resolvedType : undefined;
|
|
157
|
+
const convert = Int64Types.has(field.type)
|
|
158
|
+
? valueConverter(fieldRepresentation(field, representation), UnsignedTypes.has(field.type))
|
|
159
|
+
: undefined;
|
|
160
|
+
if (field.map) {
|
|
161
|
+
const convertKey = Int64Types.has(field.keyType)
|
|
162
|
+
? keyConverter(UnsignedTypes.has(field.keyType))
|
|
163
|
+
: undefined;
|
|
164
|
+
if (convert === undefined && convertKey === undefined && messageType === undefined) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return { name: field.name, shape: "map", convert, convertKey, type: messageType };
|
|
168
|
+
}
|
|
169
|
+
if (convert === undefined && messageType === undefined) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const shape = field.repeated ? "repeated" : "single";
|
|
173
|
+
return { name: field.name, shape, convert, type: messageType };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The representation of a 64-bit field: its `jstype` option's, or the root's. */
|
|
177
|
+
function fieldRepresentation(field, representation) {
|
|
178
|
+
switch (field.options?.jstype) {
|
|
179
|
+
case "JS_STRING":
|
|
180
|
+
return "string";
|
|
181
|
+
case "JS_NUMBER":
|
|
182
|
+
return "number";
|
|
183
|
+
default:
|
|
184
|
+
return representation;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Returns a function converting a decoded 64-bit value, or undefined for Long objects. */
|
|
189
|
+
function valueConverter(representation, unsigned) {
|
|
190
|
+
switch (representation) {
|
|
191
|
+
case "bigint":
|
|
192
|
+
return (value) => int64ToBigInt(value, unsigned);
|
|
193
|
+
case "number":
|
|
194
|
+
return (value) => Number(int64ToBigInt(value, unsigned));
|
|
195
|
+
case "string":
|
|
196
|
+
return (value) => int64ToBigInt(value, unsigned).toString();
|
|
197
|
+
default:
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Returns a function converting a decoded 64-bit map key, an 8-byte hash, to decimal. */
|
|
203
|
+
function keyConverter(unsigned) {
|
|
204
|
+
return (key) => int64ToBigInt(protobuf.util.longFromHash(key, unsigned), unsigned).toString();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Makes unset proto3 `optional` 64-bit fields of `type`'s messages read null, as other fields with
|
|
209
|
+
* presence do: protobuf.js turns their null default into a Long zero.
|
|
210
|
+
*/
|
|
211
|
+
function restoreNullDefaults(type) {
|
|
212
|
+
for (const field of type.fieldsArray) {
|
|
213
|
+
if (Int64Types.has(field.type) && field.options?.proto3_optional && !field.repeated) {
|
|
214
|
+
type.ctor.prototype[field.name] = null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Converts the defaults that `type`'s messages read from their prototype for unset singular 64-bit
|
|
221
|
+
* fields, so that an unset field reads like a set one.
|
|
222
|
+
*/
|
|
223
|
+
function patchDefaults(type, plan) {
|
|
224
|
+
const prototype = type.ctor.prototype;
|
|
225
|
+
for (const step of plan) {
|
|
226
|
+
// A field with presence, such as a proto3 optional one, reads null when unset.
|
|
227
|
+
if (step.shape === "single" && step.convert !== undefined && prototype[step.name] != null) {
|
|
228
|
+
prototype[step.name] = step.convert(prototype[step.name]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function applyPlan(plan, message) {
|
|
234
|
+
for (const step of plan) {
|
|
235
|
+
if (!Object.hasOwn(message, step.name)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const value = message[step.name];
|
|
239
|
+
if (value == null) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
switch (step.shape) {
|
|
243
|
+
case "single":
|
|
244
|
+
message[step.name] =
|
|
245
|
+
step.convert === undefined ? applyPlan(step.nested, value) : step.convert(value);
|
|
246
|
+
break;
|
|
247
|
+
case "repeated":
|
|
248
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
249
|
+
value[index] =
|
|
250
|
+
step.convert === undefined
|
|
251
|
+
? applyPlan(step.nested, value[index])
|
|
252
|
+
: step.convert(value[index]);
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case "map":
|
|
256
|
+
if (step.convertKey === undefined) {
|
|
257
|
+
convertMapValues(step, value);
|
|
258
|
+
} else {
|
|
259
|
+
message[step.name] = convertMap(step, value);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return message;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Returns a copy of a decoded map with converted keys and values. */
|
|
268
|
+
function convertMap(step, map) {
|
|
269
|
+
const converted = {};
|
|
270
|
+
for (const [key, value] of Object.entries(map)) {
|
|
271
|
+
converted[step.convertKey(key)] = convertMapValue(step, value);
|
|
272
|
+
}
|
|
273
|
+
return converted;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Converts the values of a decoded map in place. */
|
|
277
|
+
function convertMapValues(step, map) {
|
|
278
|
+
for (const [key, value] of Object.entries(map)) {
|
|
279
|
+
map[key] = convertMapValue(step, value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function convertMapValue(step, value) {
|
|
284
|
+
if (step.convert !== undefined) {
|
|
285
|
+
return step.convert(value);
|
|
286
|
+
}
|
|
287
|
+
return step.nested !== undefined && value != null ? applyPlan(step.nested, value) : value;
|
|
288
|
+
}
|