@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/generator.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import protobuf from "protobufjs";
|
|
2
2
|
|
|
3
|
+
import { Int64Representations } from "./int64.js";
|
|
4
|
+
|
|
3
5
|
const DESCRIPTOR_PROTO = String.raw`
|
|
4
6
|
syntax = "proto2";
|
|
5
7
|
|
|
@@ -107,6 +109,12 @@ message TrevrpcMethodOptions {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
message FieldOptions {
|
|
112
|
+
enum JSType {
|
|
113
|
+
JS_NORMAL = 0;
|
|
114
|
+
JS_STRING = 1;
|
|
115
|
+
JS_NUMBER = 2;
|
|
116
|
+
}
|
|
117
|
+
optional JSType jstype = 6;
|
|
110
118
|
optional bool debug_redact = 16;
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -195,6 +203,24 @@ const GeneratedTypeImports = [
|
|
|
195
203
|
"ResponseStream",
|
|
196
204
|
];
|
|
197
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Well-known types whose declarations the runtime exports, so that generated files share them
|
|
208
|
+
* instead of declaring their own: the full name and whether it holds 64-bit integers, which the
|
|
209
|
+
* runtime declares as bigint, so it is shared only when that is the representation.
|
|
210
|
+
*/
|
|
211
|
+
const SharedWellKnownTypes = new Map([
|
|
212
|
+
["google.protobuf.Timestamp", { int64: true }],
|
|
213
|
+
["google.protobuf.Duration", { int64: true }],
|
|
214
|
+
["google.protobuf.Empty", { int64: false }],
|
|
215
|
+
["google.protobuf.Struct", { int64: false }],
|
|
216
|
+
["google.protobuf.Value", { int64: false }],
|
|
217
|
+
["google.protobuf.ListValue", { int64: false }],
|
|
218
|
+
["google.protobuf.NullValue", { int64: false }],
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
/** FieldOptions.JSType values. */
|
|
222
|
+
const JSType = Object.freeze({ normal: 0, string: 1, number: 2 });
|
|
223
|
+
|
|
198
224
|
const RESERVED_WORDS = new Set([
|
|
199
225
|
"break",
|
|
200
226
|
"case",
|
|
@@ -245,7 +271,6 @@ export function generate(request) {
|
|
|
245
271
|
const filesToGenerate = new Set(request.fileToGenerate ?? []);
|
|
246
272
|
const protoFiles = request.protoFile ?? [];
|
|
247
273
|
const rootJson = buildProtobufJson(protoFiles);
|
|
248
|
-
const typeNames = declarationTypeNames(protoFiles);
|
|
249
274
|
const redacted = redactedTypes(protoFiles);
|
|
250
275
|
const files = [];
|
|
251
276
|
|
|
@@ -255,13 +280,14 @@ export function generate(request) {
|
|
|
255
280
|
}
|
|
256
281
|
|
|
257
282
|
const fileName = outputFileName(file.name, options.fileSuffix);
|
|
283
|
+
const model = declarationModel(protoFiles, file, options);
|
|
258
284
|
files.push({
|
|
259
285
|
name: fileName,
|
|
260
|
-
content: generateFile(file, rootJson, redacted, options),
|
|
286
|
+
content: generateFile(file, rootJson, model, redacted, options),
|
|
261
287
|
});
|
|
262
288
|
files.push({
|
|
263
289
|
name: declarationFileName(fileName),
|
|
264
|
-
content: generateDeclarationFile(
|
|
290
|
+
content: generateDeclarationFile(file, model, redacted, options),
|
|
265
291
|
});
|
|
266
292
|
}
|
|
267
293
|
|
|
@@ -277,6 +303,7 @@ export function parseOptions(parameter) {
|
|
|
277
303
|
runtimeImport: "@trevrpc/trevrpc-js",
|
|
278
304
|
runtimeTypeImport: null,
|
|
279
305
|
fileSuffix: ".trevrpc.js",
|
|
306
|
+
int64: "bigint",
|
|
280
307
|
};
|
|
281
308
|
|
|
282
309
|
for (const option of parameter.split(",")) {
|
|
@@ -301,6 +328,14 @@ export function parseOptions(parameter) {
|
|
|
301
328
|
case "file_suffix":
|
|
302
329
|
options.fileSuffix = value;
|
|
303
330
|
break;
|
|
331
|
+
case "int64":
|
|
332
|
+
if (!Int64Representations.includes(value)) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`invalid trevrpc-js int64 ${JSON.stringify(value)}; expected one of ${Int64Representations.join(", ")}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
options.int64 = value;
|
|
338
|
+
break;
|
|
304
339
|
default:
|
|
305
340
|
throw new Error(`unknown trevrpc-js option ${JSON.stringify(key)}`);
|
|
306
341
|
}
|
|
@@ -334,40 +369,63 @@ export function buildProtobufJson(files) {
|
|
|
334
369
|
return root;
|
|
335
370
|
}
|
|
336
371
|
|
|
337
|
-
function generateFile(file, rootJson, redacted, options) {
|
|
372
|
+
function generateFile(file, rootJson, model, redacted, options) {
|
|
338
373
|
const services = (file.service ?? []).map((service) =>
|
|
339
374
|
serviceDescriptor(file, service, redacted),
|
|
340
375
|
);
|
|
341
376
|
const serviceExports = services.map((service) => generateService(service)).join("\n");
|
|
377
|
+
const enumExports = model.enums
|
|
378
|
+
.filter(({ shared }) => !shared)
|
|
379
|
+
.map(generateEnum)
|
|
380
|
+
.join("\n\n");
|
|
342
381
|
|
|
343
382
|
return `// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.\n\nimport { createRoot, createServiceClient } from ${JSON.stringify(
|
|
344
383
|
options.runtimeImport,
|
|
345
|
-
)};\n\nexport const root = createRoot(${JSON.stringify(rootJson, null, 2)});\n\n${serviceExports}`;
|
|
384
|
+
)};\n\nexport const root = createRoot(${JSON.stringify(rootJson, null, 2)}, { int64: ${JSON.stringify(options.int64)} });\n\n${enumExports === "" ? "" : `${enumExports}\n\n`}${serviceExports}`;
|
|
346
385
|
}
|
|
347
386
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
387
|
+
/** The runtime value of an enum: a frozen object of its values by name. */
|
|
388
|
+
function generateEnum({ fullName, name, values }) {
|
|
389
|
+
const entries = values.map(({ key, number }) => ` ${typescriptPropertyName(key)}: ${number},`);
|
|
390
|
+
return `/** Values of the ${fullName} enum. */\nexport const ${name} = Object.freeze({\n${entries.join("\n")}\n});`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function generateDeclarationFile(file, model, redacted, options) {
|
|
394
|
+
const context = { model, runtimeTypes: new Set(), usesLong: false };
|
|
395
|
+
const enums = model.enums
|
|
396
|
+
.filter(({ shared }) => !shared)
|
|
397
|
+
.map(generateEnumDeclaration)
|
|
398
|
+
.join("\n\n");
|
|
399
|
+
const messages = model.messages
|
|
400
|
+
.filter(({ shared }) => !shared)
|
|
401
|
+
.map((message) => generateMessageDeclarations(message, context))
|
|
352
402
|
.join("\n\n");
|
|
353
403
|
const services = (file.service ?? [])
|
|
354
404
|
.map((service) => serviceDescriptor(file, service, redacted))
|
|
355
|
-
.map((service) => generateServiceDeclaration(service,
|
|
405
|
+
.map((service) => generateServiceDeclaration(service, context))
|
|
356
406
|
.join("\n\n");
|
|
357
|
-
const sections = [messages, services].filter(Boolean).join("\n\n");
|
|
358
|
-
const protobufImports =
|
|
407
|
+
const sections = [enums, messages, services].filter(Boolean).join("\n\n");
|
|
408
|
+
const protobufImports = context.usesLong ? "Long, Root" : "Root";
|
|
359
409
|
const runtimeImports = [
|
|
360
410
|
"CallOptions",
|
|
411
|
+
"ClientOptions",
|
|
361
412
|
"RpcServiceDescriptor",
|
|
362
413
|
"Transport",
|
|
414
|
+
...context.runtimeTypes,
|
|
363
415
|
...GeneratedTypeImports.filter((name) => sections.includes(`${name}<`)),
|
|
364
|
-
].sort();
|
|
416
|
+
].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
365
417
|
|
|
366
418
|
return `// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.\n\nimport type { ${protobufImports} } from "protobufjs";\n\nimport type { ${runtimeImports.join(", ")} } from ${JSON.stringify(
|
|
367
419
|
options.runtimeTypeImport,
|
|
368
420
|
)};\n\nexport declare const root: Root;\n\n${sections}\n`;
|
|
369
421
|
}
|
|
370
422
|
|
|
423
|
+
/** The declaration of an enum's runtime value and of the type of its values. */
|
|
424
|
+
function generateEnumDeclaration({ fullName, name, values }) {
|
|
425
|
+
const entries = values.map(({ key, number }) => `${typescriptPropertyName(key)}: ${number}`);
|
|
426
|
+
return `/** Values of the ${fullName} enum. */\nexport declare const ${name}: Readonly<{ ${entries.join("; ")} }>;\n/** A value of the ${fullName} enum. */\nexport type ${name} = (typeof ${name})[keyof typeof ${name}];`;
|
|
427
|
+
}
|
|
428
|
+
|
|
371
429
|
function generateService(service) {
|
|
372
430
|
const className = `${pascalIdentifier(service.name)}Client`;
|
|
373
431
|
const factoryName = `create${className}`;
|
|
@@ -385,19 +443,20 @@ function generateServiceMethod(jsName, method) {
|
|
|
385
443
|
return ` /** Calls the ${method.name} RPC. */\n ${jsName}(request, options = {}) {\n return this._client.${jsName}(request, options);\n }`;
|
|
386
444
|
}
|
|
387
445
|
|
|
388
|
-
function generateServiceDeclaration(service,
|
|
446
|
+
function generateServiceDeclaration(service, context) {
|
|
389
447
|
const className = `${pascalIdentifier(service.name)}Client`;
|
|
390
448
|
const factoryName = `create${className}`;
|
|
391
449
|
const methods = Object.entries(service.methods)
|
|
392
|
-
.map(([jsName, method]) => generateMethodDeclaration(jsName, method,
|
|
450
|
+
.map(([jsName, method]) => generateMethodDeclaration(jsName, method, context))
|
|
393
451
|
.join("\n");
|
|
394
452
|
|
|
395
|
-
return `/** Service descriptor for the ${service.name} service. */\nexport declare const ${service.exportName}: RpcServiceDescriptor;\n\n/** Client for the ${service.name} service. */\nexport declare class ${className} {\n /** Creates a client for the ${service.name} service. */\n constructor(transport: Transport, options?:
|
|
453
|
+
return `/** Service descriptor for the ${service.name} service. */\nexport declare const ${service.exportName}: RpcServiceDescriptor;\n\n/** Client for the ${service.name} service. */\nexport declare class ${className} {\n /** Creates a client for the ${service.name} service. */\n constructor(transport: Transport, options?: ClientOptions);\n\n${methods}\n}\n\n/** Creates a client for the ${service.name} service. */\nexport declare function ${factoryName}(transport: Transport, options?: ClientOptions): ${className};`;
|
|
396
454
|
}
|
|
397
455
|
|
|
398
|
-
function generateMethodDeclaration(jsName, method,
|
|
399
|
-
|
|
400
|
-
const
|
|
456
|
+
function generateMethodDeclaration(jsName, method, context) {
|
|
457
|
+
// Requests take the input shape; responses are decoded messages.
|
|
458
|
+
const inputType = messageTypeReference(method.inputType, "input", context);
|
|
459
|
+
const outputType = messageTypeReference(method.outputType, "output", context);
|
|
401
460
|
|
|
402
461
|
switch (method.kind) {
|
|
403
462
|
case "unary":
|
|
@@ -551,19 +610,122 @@ function redactedTypes(files) {
|
|
|
551
610
|
return redacted;
|
|
552
611
|
}
|
|
553
612
|
|
|
554
|
-
|
|
555
|
-
|
|
613
|
+
/**
|
|
614
|
+
* The declarations of a generated file: its own messages and enums and every type they and its
|
|
615
|
+
* services use, directly or through other messages. `messages` holds each message's `name` (the
|
|
616
|
+
* decoded shape) and `initName` (the input shape), `enums` each enum's `name` and `values`, and
|
|
617
|
+
* `names` maps full names to them. Well-known types the runtime declares are `shared`: their names
|
|
618
|
+
* are reserved first and refer to the runtime's declarations.
|
|
619
|
+
*/
|
|
620
|
+
function declarationModel(files, file, options) {
|
|
556
621
|
const usedNames = new Set();
|
|
622
|
+
const names = new Map();
|
|
623
|
+
const isShared = (fullName) => {
|
|
624
|
+
const shared = SharedWellKnownTypes.get(fullName);
|
|
625
|
+
return shared !== undefined && (!shared.int64 || options.int64 === "bigint");
|
|
626
|
+
};
|
|
627
|
+
const used = usedTypes(files, file);
|
|
628
|
+
const messages = collectMessages(files)
|
|
629
|
+
.filter(({ fullName, message }) => used.has(fullName) && !message.options?.mapEntry)
|
|
630
|
+
.map((entry) => ({ ...entry, shared: isShared(entry.fullName) }));
|
|
631
|
+
const enums = collectEnums(files)
|
|
632
|
+
.filter(({ fullName }) => used.has(fullName))
|
|
633
|
+
.map((entry) => ({ ...entry, shared: isShared(entry.fullName) }));
|
|
634
|
+
const declared = [...messages, ...enums];
|
|
635
|
+
|
|
636
|
+
for (const entry of declared.filter(({ shared }) => shared)) {
|
|
637
|
+
entry.name = uniqueName(lastTypeSegment(entry.fullName), usedNames);
|
|
638
|
+
}
|
|
639
|
+
for (const entry of declared.filter(({ shared }) => !shared)) {
|
|
640
|
+
entry.name = uniqueName(pascalIdentifier(lastTypeSegment(entry.fullName)), usedNames);
|
|
641
|
+
}
|
|
642
|
+
for (const message of messages) {
|
|
643
|
+
message.initName = message.shared
|
|
644
|
+
? `${message.name}Init`
|
|
645
|
+
: uniqueName(`${message.name}Init`, usedNames);
|
|
646
|
+
}
|
|
647
|
+
for (const entry of declared) {
|
|
648
|
+
names.set(entry.fullName, entry);
|
|
649
|
+
}
|
|
650
|
+
for (const enumEntry of enums) {
|
|
651
|
+
enumEntry.values = enumValues(enumEntry);
|
|
652
|
+
}
|
|
653
|
+
return { messages, enums, names, int64: options.int64 };
|
|
654
|
+
}
|
|
557
655
|
|
|
558
|
-
|
|
559
|
-
|
|
656
|
+
/**
|
|
657
|
+
* The full names of the types a generated file declares: the messages and enums `file` defines,
|
|
658
|
+
* the request and response types of its services, and every type their fields use, transitively.
|
|
659
|
+
*/
|
|
660
|
+
function usedTypes(files, file) {
|
|
661
|
+
const messages = new Map(
|
|
662
|
+
collectMessages(files).map(({ fullName, message }) => [fullName, message]),
|
|
663
|
+
);
|
|
664
|
+
const used = new Set();
|
|
665
|
+
const pending = [
|
|
666
|
+
...collectMessages([file]).map(({ fullName }) => fullName),
|
|
667
|
+
...collectEnums([file]).map(({ fullName }) => fullName),
|
|
668
|
+
...(file.service ?? []).flatMap((service) =>
|
|
669
|
+
(service.method ?? []).flatMap((method) =>
|
|
670
|
+
[method.inputType, method.outputType].map(stripLeadingDot),
|
|
671
|
+
),
|
|
672
|
+
),
|
|
673
|
+
];
|
|
674
|
+
while (pending.length > 0) {
|
|
675
|
+
const fullName = pending.pop();
|
|
676
|
+
if (used.has(fullName)) {
|
|
560
677
|
continue;
|
|
561
678
|
}
|
|
562
|
-
|
|
563
|
-
|
|
679
|
+
used.add(fullName);
|
|
680
|
+
for (const field of messages.get(fullName)?.field ?? []) {
|
|
681
|
+
if (field.typeName != null) {
|
|
682
|
+
pending.push(stripLeadingDot(field.typeName));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
564
685
|
}
|
|
686
|
+
return used;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* The values of an enum, each with the `key` it has in the exported object: its name without the
|
|
691
|
+
* enum's prefix (`TIME_INTERVAL_HOUR` of `TimeInterval` is `HOUR`) when every value has that prefix
|
|
692
|
+
* and every key that leaves is a distinct identifier, as protobuf-es does; its full name otherwise.
|
|
693
|
+
*/
|
|
694
|
+
function enumValues({ fullName, enumType }) {
|
|
695
|
+
const values = (enumType.value ?? []).map((value) => ({
|
|
696
|
+
name: value.name ?? "",
|
|
697
|
+
number: value.number ?? 0,
|
|
698
|
+
}));
|
|
699
|
+
const prefix = `${upperSnakeCase(lastTypeSegment(fullName))}_`;
|
|
700
|
+
const stripped = values.map(({ name }) => name.slice(prefix.length));
|
|
701
|
+
const strip =
|
|
702
|
+
values.length > 0 &&
|
|
703
|
+
values.every(({ name }) => name.startsWith(prefix)) &&
|
|
704
|
+
stripped.every((key) => /^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) &&
|
|
705
|
+
new Set(stripped).size === stripped.length;
|
|
706
|
+
return values.map((value, index) => ({ ...value, key: strip ? stripped[index] : value.name }));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Converts a PascalCase or camelCase name to UPPER_SNAKE_CASE. */
|
|
710
|
+
function upperSnakeCase(name) {
|
|
711
|
+
return name.replace(/([a-z0-9])([A-Z])/gu, "$1_$2").toUpperCase();
|
|
712
|
+
}
|
|
565
713
|
|
|
566
|
-
|
|
714
|
+
/** Every enum of `files`, top-level and nested in messages, with its full name. */
|
|
715
|
+
function collectEnums(files) {
|
|
716
|
+
const enums = [];
|
|
717
|
+
for (const file of files) {
|
|
718
|
+
const scope = file.package ?? "";
|
|
719
|
+
for (const enumType of file.enumType ?? []) {
|
|
720
|
+
enums.push({ fullName: joinTypeName(scope, enumType.name), enumType });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
for (const { fullName, message } of collectMessages(files)) {
|
|
724
|
+
for (const enumType of message.enumType ?? []) {
|
|
725
|
+
enums.push({ fullName: joinTypeName(fullName, enumType.name), enumType });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return enums;
|
|
567
729
|
}
|
|
568
730
|
|
|
569
731
|
function collectMessages(files) {
|
|
@@ -592,29 +754,35 @@ function collectMessage(messages, fullName, message) {
|
|
|
592
754
|
}
|
|
593
755
|
}
|
|
594
756
|
|
|
595
|
-
|
|
596
|
-
|
|
757
|
+
/**
|
|
758
|
+
* The two declarations of a message: the decoded shape that calls return and streams yield, in
|
|
759
|
+
* which every field reads a value (scalars and enums their default, repeated fields and maps an
|
|
760
|
+
* empty array and object, message and `optional` fields null when unset), and the input shape that
|
|
761
|
+
* requests take, in which every field may be left out.
|
|
762
|
+
*/
|
|
763
|
+
function generateMessageDeclarations({ fullName, message, name, initName }, context) {
|
|
597
764
|
const mapEntries = new Map(
|
|
598
765
|
(message.nestedType ?? [])
|
|
599
766
|
.filter((nested) => nested.options?.mapEntry)
|
|
600
767
|
.map((nested) => [nested.name, nested]),
|
|
601
768
|
);
|
|
602
|
-
const fields = (
|
|
603
|
-
.
|
|
604
|
-
|
|
769
|
+
const fields = (shape) =>
|
|
770
|
+
(message.field ?? [])
|
|
771
|
+
.map((field) => generateFieldDeclaration(field, mapEntries, shape, context))
|
|
772
|
+
.join("\n");
|
|
773
|
+
const body = (shape) => {
|
|
774
|
+
const declared = fields(shape);
|
|
775
|
+
return declared === "" ? "{}" : `{\n${declared}\n}`;
|
|
776
|
+
};
|
|
605
777
|
|
|
606
|
-
return
|
|
778
|
+
return `/** A decoded ${fullName} message. */\nexport interface ${name} ${body("output")}\n\n/** A ${fullName} message to send; every field may be left out. */\nexport interface ${initName} ${body("input")}`;
|
|
607
779
|
}
|
|
608
780
|
|
|
609
|
-
function generateFieldDeclaration(field, mapEntries,
|
|
610
|
-
const optional = field.label
|
|
781
|
+
function generateFieldDeclaration(field, mapEntries, shape, context) {
|
|
782
|
+
const optional = shape === "input" && field.label !== FieldLabel.Required ? "?" : "";
|
|
611
783
|
const nullable = decodesAbsentAsNull(field) ? " | null" : "";
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
mapEntries,
|
|
615
|
-
typeNames,
|
|
616
|
-
currentFullName,
|
|
617
|
-
)}${nullable};`;
|
|
784
|
+
const type = fieldTypeScriptType(field, mapEntries, shape, context);
|
|
785
|
+
return ` ${typescriptPropertyName(fieldJsonName(field))}${optional}: ${type}${nullable};`;
|
|
618
786
|
}
|
|
619
787
|
|
|
620
788
|
/** Reports whether protobuf.js decodes the field as null when it is absent. */
|
|
@@ -629,19 +797,23 @@ function decodesAbsentAsNull(field) {
|
|
|
629
797
|
);
|
|
630
798
|
}
|
|
631
799
|
|
|
632
|
-
function fieldTypeScriptType(field, mapEntries,
|
|
800
|
+
function fieldTypeScriptType(field, mapEntries, shape, context) {
|
|
633
801
|
const mapEntry = mapEntryForField(field, mapEntries);
|
|
634
802
|
if (mapEntry != null) {
|
|
635
803
|
const [, valueField] = mapEntry.field ?? [];
|
|
636
|
-
|
|
637
|
-
return `Record<string, ${valueType}>`;
|
|
804
|
+
return `Record<string, ${scalarTypeScriptType(valueField, shape, context)}>`;
|
|
638
805
|
}
|
|
639
806
|
|
|
640
|
-
const scalarType = scalarTypeScriptType(field,
|
|
641
|
-
return field.label === FieldLabel.Repeated ? `${scalarType}[]` : scalarType;
|
|
807
|
+
const scalarType = scalarTypeScriptType(field, shape, context);
|
|
808
|
+
return field.label === FieldLabel.Repeated ? `${arrayElementType(scalarType)}[]` : scalarType;
|
|
642
809
|
}
|
|
643
810
|
|
|
644
|
-
|
|
811
|
+
/** Parenthesizes a union so that it can be an array's element type. */
|
|
812
|
+
function arrayElementType(type) {
|
|
813
|
+
return type.includes(" | ") ? `(${type})` : type;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function scalarTypeScriptType(field, shape, context) {
|
|
645
817
|
switch (field?.type) {
|
|
646
818
|
case FieldType.Double:
|
|
647
819
|
case FieldType.Float:
|
|
@@ -650,14 +822,15 @@ function scalarTypeScriptType(field, typeNames, currentFullName) {
|
|
|
650
822
|
case FieldType.Sint32:
|
|
651
823
|
case FieldType.Fixed32:
|
|
652
824
|
case FieldType.Sfixed32:
|
|
653
|
-
case FieldType.Enum:
|
|
654
825
|
return "number";
|
|
826
|
+
case FieldType.Enum:
|
|
827
|
+
return declaredName(field.typeName, "output", context) ?? "number";
|
|
655
828
|
case FieldType.Int64:
|
|
656
829
|
case FieldType.Uint64:
|
|
657
830
|
case FieldType.Sint64:
|
|
658
831
|
case FieldType.Fixed64:
|
|
659
832
|
case FieldType.Sfixed64:
|
|
660
|
-
return
|
|
833
|
+
return int64TypeScriptType(field, shape, context);
|
|
661
834
|
case FieldType.Bool:
|
|
662
835
|
return "boolean";
|
|
663
836
|
case FieldType.String:
|
|
@@ -666,36 +839,63 @@ function scalarTypeScriptType(field, typeNames, currentFullName) {
|
|
|
666
839
|
return "Uint8Array";
|
|
667
840
|
case FieldType.Group:
|
|
668
841
|
case FieldType.Message:
|
|
669
|
-
return
|
|
670
|
-
resolveTypeName(field.typeName, typeNames, currentFullName),
|
|
671
|
-
typeNames,
|
|
672
|
-
);
|
|
842
|
+
return messageTypeReference(field.typeName, shape, context);
|
|
673
843
|
default:
|
|
674
844
|
throw new Error(`unsupported protobuf field type ${field?.type}`);
|
|
675
845
|
}
|
|
676
846
|
}
|
|
677
847
|
|
|
678
|
-
|
|
679
|
-
|
|
848
|
+
/**
|
|
849
|
+
* The type of a 64-bit field: the representation the decoded shape has (the field's `jstype`, or
|
|
850
|
+
* the generator's `int64` option), and every representation the encoder accepts in the input.
|
|
851
|
+
*/
|
|
852
|
+
function int64TypeScriptType(field, shape, context) {
|
|
853
|
+
const representation = int64Representation(field, context.model.int64);
|
|
854
|
+
if (representation === "long") {
|
|
855
|
+
context.usesLong = true;
|
|
856
|
+
}
|
|
857
|
+
if (shape === "input") {
|
|
858
|
+
return representation === "long"
|
|
859
|
+
? "bigint | number | string | Long"
|
|
860
|
+
: "bigint | number | string";
|
|
861
|
+
}
|
|
862
|
+
return representation === "long" ? "Long" : representation;
|
|
680
863
|
}
|
|
681
864
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
865
|
+
/** How decoded messages represent a 64-bit field: by its `jstype`, else by the `int64` option. */
|
|
866
|
+
function int64Representation(field, representation) {
|
|
867
|
+
switch (field.options?.jstype) {
|
|
868
|
+
case JSType.string:
|
|
869
|
+
return "string";
|
|
870
|
+
case JSType.number:
|
|
871
|
+
return "number";
|
|
872
|
+
default:
|
|
873
|
+
return representation;
|
|
686
874
|
}
|
|
875
|
+
}
|
|
687
876
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
scope.pop();
|
|
696
|
-
}
|
|
877
|
+
/**
|
|
878
|
+
* The declared name of a message type in `shape`, `"output"` or `"input"`, noting the runtime
|
|
879
|
+
* declarations it uses.
|
|
880
|
+
*/
|
|
881
|
+
function messageTypeReference(typeName, shape, context) {
|
|
882
|
+
return declaredName(typeName, shape, context) ?? "Record<string, unknown>";
|
|
883
|
+
}
|
|
697
884
|
|
|
698
|
-
|
|
885
|
+
/**
|
|
886
|
+
* The declared name of a message or enum type in `shape` (enums have one for both), or undefined
|
|
887
|
+
* for a type without a declaration. A name the runtime declares is noted for import.
|
|
888
|
+
*/
|
|
889
|
+
function declaredName(typeName, shape, context) {
|
|
890
|
+
const entry = context.model.names.get(stripLeadingDot(typeName));
|
|
891
|
+
if (entry === undefined) {
|
|
892
|
+
return undefined;
|
|
893
|
+
}
|
|
894
|
+
const name = shape === "input" && entry.initName !== undefined ? entry.initName : entry.name;
|
|
895
|
+
if (entry.shared) {
|
|
896
|
+
context.runtimeTypes.add(name);
|
|
897
|
+
}
|
|
898
|
+
return name;
|
|
699
899
|
}
|
|
700
900
|
|
|
701
901
|
function joinTypeName(prefix, name) {
|
|
@@ -807,6 +1007,13 @@ function fieldToJson(field, mapEntries) {
|
|
|
807
1007
|
// only when this option marks its synthetic oneof.
|
|
808
1008
|
json.options = { ...json.options, proto3_optional: true };
|
|
809
1009
|
}
|
|
1010
|
+
// The runtime reads it to decode the field as a string or number instead of the root's int64
|
|
1011
|
+
// representation.
|
|
1012
|
+
if (field.options?.jstype === JSType.string) {
|
|
1013
|
+
json.options = { ...json.options, jstype: "JS_STRING" };
|
|
1014
|
+
} else if (field.options?.jstype === JSType.number) {
|
|
1015
|
+
json.options = { ...json.options, jstype: "JS_NUMBER" };
|
|
1016
|
+
}
|
|
810
1017
|
|
|
811
1018
|
return json;
|
|
812
1019
|
}
|