ts-proto 1.151.0 → 1.152.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 (27) hide show
  1. package/build/integration/codegen.js +63 -0
  2. package/build/{generate-grpc-web.js → src/generate-grpc-web.js} +19 -21
  3. package/build/{generate-services.js → src/generate-services.js} +1 -1
  4. package/build/{generate-type-registry.js → src/generate-type-registry.js} +15 -2
  5. package/build/{main.js → src/main.js} +33 -25
  6. package/build/{options.js → src/options.js} +5 -1
  7. package/build/{types.js → src/types.js} +17 -17
  8. package/build/tests/case-test.js +52 -0
  9. package/build/tests/options-test.js +148 -0
  10. package/build/tests/types-test.js +56 -0
  11. package/build/tests/utils-test.js +65 -0
  12. package/package.json +4 -2
  13. /package/build/{case.js → src/case.js} +0 -0
  14. /package/build/{context.js → src/context.js} +0 -0
  15. /package/build/{encode.js → src/encode.js} +0 -0
  16. /package/build/{enums.js → src/enums.js} +0 -0
  17. /package/build/{generate-async-iterable.js → src/generate-async-iterable.js} +0 -0
  18. /package/build/{generate-generic-service-definition.js → src/generate-generic-service-definition.js} +0 -0
  19. /package/build/{generate-grpc-js.js → src/generate-grpc-js.js} +0 -0
  20. /package/build/{generate-nestjs.js → src/generate-nestjs.js} +0 -0
  21. /package/build/{generate-nice-grpc.js → src/generate-nice-grpc.js} +0 -0
  22. /package/build/{generate-struct-wrappers.js → src/generate-struct-wrappers.js} +0 -0
  23. /package/build/{plugin.js → src/plugin.js} +0 -0
  24. /package/build/{schema.js → src/schema.js} +0 -0
  25. /package/build/{sourceInfo.js → src/sourceInfo.js} +0 -0
  26. /package/build/{utils.js → src/utils.js} +0 -0
  27. /package/build/{visit.js → src/visit.js} +0 -0
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ts_proto_descriptors_1 = require("ts-proto-descriptors");
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const util_1 = require("util");
7
+ const main_1 = require("../src/main");
8
+ const types_1 = require("../src/types");
9
+ const utils_1 = require("../src/utils");
10
+ const options_1 = require("../src/options");
11
+ const generate_type_registry_1 = require("../src/generate-type-registry");
12
+ /**
13
+ * Generates output for our integration tests from their example proto files.
14
+ *
15
+ * We use snapshots of the protoc CodeGeneratorRequest's that are captured
16
+ * by running ./update_proto_bins.sh, just because various machines/CI/etc
17
+ * may not have the `protoc` compiler on their path.
18
+ */
19
+ async function main() {
20
+ const dir = process.argv[2];
21
+ const file = process.argv[3];
22
+ const param = process.argv[4];
23
+ await generate(file, dir, param);
24
+ }
25
+ async function generate(binFile, baseDir, parameter) {
26
+ const stdin = await (0, util_1.promisify)(fs_1.readFile)(binFile);
27
+ const request = ts_proto_descriptors_1.CodeGeneratorRequest.decode(stdin);
28
+ request.parameter = parameter;
29
+ const options = (0, options_1.optionsFromParameter)(parameter || "");
30
+ const typeMap = (0, types_1.createTypeMap)(request, options);
31
+ for (let file of request.protoFile) {
32
+ // Make a different utils per file to track per-file usage
33
+ if (options.M[file.name]) {
34
+ continue;
35
+ }
36
+ const utils = (0, main_1.makeUtils)(options);
37
+ const ctx = { options, typeMap, utils };
38
+ const [path, code] = (0, main_1.generateFile)(ctx, file);
39
+ const filePath = `${baseDir}/${path}`;
40
+ const dirPath = (0, path_1.parse)(filePath).dir;
41
+ await (0, util_1.promisify)(fs_1.mkdir)(dirPath, { recursive: true }).catch(() => { });
42
+ await (0, util_1.promisify)(fs_1.writeFile)(filePath, code.toString({ ...(0, options_1.getTsPoetOpts)(options), path }));
43
+ }
44
+ if (options.outputTypeRegistry) {
45
+ const utils = (0, main_1.makeUtils)(options);
46
+ const ctx = { options, typeMap, utils };
47
+ const path = "typeRegistry.ts";
48
+ const code = (0, generate_type_registry_1.generateTypeRegistry)(ctx);
49
+ const filePath = `${baseDir}/${path}`;
50
+ await (0, util_1.promisify)(fs_1.writeFile)(filePath, code.toString({ ...(0, options_1.getTsPoetOpts)(options), path }));
51
+ }
52
+ if (options.outputIndex) {
53
+ for (const [path, code] of (0, utils_1.generateIndexFiles)(request.protoFile, options)) {
54
+ const filePath = `${baseDir}/${path}`;
55
+ const dirPath = (0, path_1.parse)(filePath).dir;
56
+ await (0, util_1.promisify)(fs_1.mkdir)(dirPath, { recursive: true }).catch(() => { });
57
+ await (0, util_1.promisify)(fs_1.writeFile)(filePath, code.toString({ ...(0, options_1.getTsPoetOpts)(options), path }));
58
+ }
59
+ }
60
+ }
61
+ main().then(() => {
62
+ console.log(`${process.argv[3]}: Done`);
63
+ });
@@ -227,17 +227,16 @@ function createPromiseUnaryMethod(ctx) {
227
227
  ${useAbortSignal ? "abortSignal?: AbortSignal," : ""}
228
228
  ): Promise<any> {
229
229
  const request = { ..._request, ...methodDesc.requestType };
230
- const maybeCombinedMetadata =
231
- metadata && this.options.metadata
232
- ? new ${BrowserHeaders}({ ...this.options?.metadata.headersMap, ...metadata?.headersMap })
233
- : metadata || this.options.metadata;
230
+ const maybeCombinedMetadata = metadata && this.options.metadata
231
+ ? new ${BrowserHeaders}({ ...this.options?.metadata.headersMap, ...metadata?.headersMap })
232
+ : metadata ?? this.options.metadata;
234
233
  return new Promise((resolve, reject) => {
235
234
  ${useAbortSignal ? `const client =` : ""} ${grpc}.unary(methodDesc, {
236
235
  request,
237
236
  host: this.host,
238
- metadata: maybeCombinedMetadata,
239
- transport: this.options.transport,
240
- debug: this.options.debug,
237
+ metadata: maybeCombinedMetadata ?? {},
238
+ ...(this.options.transport !== undefined ? {transport: this.options.transport} : {}),
239
+ debug: this.options.debug ?? false,
241
240
  onEnd: function (response) {
242
241
  if (response.status === grpc.Code.OK) {
243
242
  resolve(response.message!.toObject());
@@ -271,17 +270,16 @@ function createObservableUnaryMethod(ctx) {
271
270
  ${useAbortSignal ? "abortSignal?: AbortSignal," : ""}
272
271
  ): ${(0, types_1.observableType)(ctx)}<any> {
273
272
  const request = { ..._request, ...methodDesc.requestType };
274
- const maybeCombinedMetadata =
275
- metadata && this.options.metadata
276
- ? new ${BrowserHeaders}({ ...this.options?.metadata.headersMap, ...metadata?.headersMap })
277
- : metadata || this.options.metadata;
273
+ const maybeCombinedMetadata = metadata && this.options.metadata
274
+ ? new ${BrowserHeaders}({ ...this.options?.metadata.headersMap, ...metadata?.headersMap })
275
+ : metadata ?? this.options.metadata;
278
276
  return new Observable(observer => {
279
277
  ${useAbortSignal ? `const client =` : ""} ${grpc}.unary(methodDesc, {
280
278
  request,
281
279
  host: this.host,
282
- metadata: maybeCombinedMetadata,
283
- transport: this.options.transport,
284
- debug: this.options.debug,
280
+ metadata: maybeCombinedMetadata ?? {},
281
+ ...(this.options.transport !== undefined ? {transport: this.options.transport} : {}),
282
+ debug: this.options.debug ?? false,
285
283
  onEnd: (next) => {
286
284
  if (next.status !== 0) {
287
285
  const err = new ${ctx.utils.GrpcWebError}(next.statusMessage, next.status, next.trailers);
@@ -317,21 +315,21 @@ function createInvokeMethod(ctx) {
317
315
  metadata: grpc.Metadata | undefined,
318
316
  ${useAbortSignal ? "abortSignal?: AbortSignal," : ""}
319
317
  ): ${(0, types_1.observableType)(ctx)}<any> {
320
- const upStreamCodes = this.options.upStreamRetryCodes || [];
318
+ const upStreamCodes = this.options.upStreamRetryCodes ?? [];
321
319
  const DEFAULT_TIMEOUT_TIME: number = 3_000;
322
320
  const request = { ..._request, ...methodDesc.requestType };
323
- const maybeCombinedMetadata =
324
- metadata && this.options.metadata
321
+ const transport = this.options.streamingTransport ?? this.options.transport;
322
+ const maybeCombinedMetadata = metadata && this.options.metadata
325
323
  ? new ${BrowserHeaders}({ ...this.options?.metadata.headersMap, ...metadata?.headersMap })
326
- : metadata || this.options.metadata;
324
+ : metadata ?? this.options.metadata;
327
325
  return new Observable(observer => {
328
326
  const upStream = (() => {
329
327
  const client = ${grpc}.invoke(methodDesc, {
330
328
  host: this.host,
331
329
  request,
332
- transport: this.options.streamingTransport || this.options.transport,
333
- metadata: maybeCombinedMetadata,
334
- debug: this.options.debug,
330
+ ...(transport !== undefined ? {transport} : {}),
331
+ metadata: maybeCombinedMetadata ?? {},
332
+ debug: this.options.debug ?? false,
335
333
  onMessage: (next) => observer.next(next),
336
334
  onEnd: (code: ${grpc}.Code, message: string, trailers: ${grpc}.Metadata) => {
337
335
  if (code === 0) {
@@ -206,7 +206,7 @@ function generateBatchingRpcMethod(ctx, batchMethod) {
206
206
  // If the return type is a map, lookup each key in the result
207
207
  lambda.push((0, ts_poet_1.code) `
208
208
  return this.${methodDesc.formattedName}(ctx, request).then(res => {
209
- return ${inputFieldName}.map(key => res.${outputFieldName}[key])
209
+ return ${inputFieldName}.map(key => res.${outputFieldName}[key] ?? ${ctx.utils.fail}())
210
210
  });
211
211
  `);
212
212
  }
@@ -3,12 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateTypeRegistry = void 0;
4
4
  const ts_poet_1 = require("ts-poet");
5
5
  const utils_1 = require("./utils");
6
+ const options_1 = require("./options");
6
7
  function generateTypeRegistry(ctx) {
7
8
  const chunks = [];
8
9
  chunks.push(generateMessageType(ctx));
9
- chunks.push((0, ts_poet_1.code) `
10
+ if ((0, options_1.addTypeToMessages)(ctx.options)) {
11
+ chunks.push((0, ts_poet_1.code) `
10
12
  export type UnknownMessage = {$type: string};
11
13
  `);
14
+ }
15
+ else {
16
+ chunks.push((0, ts_poet_1.code) `
17
+ export type UnknownMessage = unknown;
18
+ `);
19
+ }
12
20
  chunks.push((0, ts_poet_1.code) `
13
21
  export const messageTypeRegistry = new Map<string, MessageType>();
14
22
  `);
@@ -19,7 +27,12 @@ exports.generateTypeRegistry = generateTypeRegistry;
19
27
  function generateMessageType(ctx) {
20
28
  const chunks = [];
21
29
  chunks.push((0, ts_poet_1.code) `export interface MessageType<Message extends UnknownMessage = UnknownMessage> {`);
22
- chunks.push((0, ts_poet_1.code) `$type: Message['$type'];`);
30
+ if ((0, options_1.addTypeToMessages)(ctx.options)) {
31
+ chunks.push((0, ts_poet_1.code) `$type: Message['$type'];`);
32
+ }
33
+ else {
34
+ chunks.push((0, ts_poet_1.code) `$type: string;`);
35
+ }
23
36
  if (ctx.options.outputEncodeMethods) {
24
37
  const Writer = (0, utils_1.impFile)(ctx.options, "Writer@protobufjs/minimal");
25
38
  const Reader = (0, utils_1.impFile)(ctx.options, "Reader@protobufjs/minimal");
@@ -279,6 +279,7 @@ function makeUtils(options) {
279
279
  ...makeNiceGrpcServerStreamingMethodResult(),
280
280
  ...makeGrpcWebErrorClass(bytes),
281
281
  ...makeExtensionClass(options),
282
+ ...makeAssertionUtils(),
282
283
  };
283
284
  }
284
285
  exports.makeUtils = makeUtils;
@@ -365,10 +366,10 @@ function makeLongUtils(options, bytes) {
365
366
  }
366
367
  function makeByteUtils() {
367
368
  const globalThis = (0, ts_poet_1.conditionalOutput)("tsProtoGlobalThis", (0, ts_poet_1.code) `
368
- declare var self: any | undefined;
369
- declare var window: any | undefined;
370
- declare var global: any | undefined;
371
- var tsProtoGlobalThis: any = (() => {
369
+ declare const self: any | undefined;
370
+ declare const window: any | undefined;
371
+ declare const global: any | undefined;
372
+ const tsProtoGlobalThis: any = (() => {
372
373
  if (typeof globalThis !== "undefined") return globalThis;
373
374
  if (typeof self !== "undefined") return self;
374
375
  if (typeof window !== "undefined") return window;
@@ -418,7 +419,7 @@ function makeDeepPartial(options, longs) {
418
419
  const maybeLong = options.forceLong === options_1.LongOption.LONG ? (0, ts_poet_1.code) ` : T extends ${longs.Long} ? string | number | Long ` : "";
419
420
  const Builtin = (0, ts_poet_1.conditionalOutput)("Builtin", (0, ts_poet_1.code) `type Builtin = Date | Function | Uint8Array | string | number | boolean |${options.forceLong === options_1.LongOption.BIGINT ? " bigint |" : ""} undefined;`);
420
421
  // Based on https://github.com/sindresorhus/type-fest/pull/259
421
- const maybeExcludeType = addTypeToMessages(options) ? `| '$type'` : "";
422
+ const maybeExcludeType = (0, options_1.addTypeToMessages)(options) ? `| '$type'` : "";
422
423
  const Exact = (0, ts_poet_1.conditionalOutput)("Exact", (0, ts_poet_1.code) `
423
424
  type KeysOfUnion<T> = T extends T ? keyof T : never;
424
425
  ${maybeExport} type Exact<P, I extends P> = P extends ${Builtin}
@@ -427,7 +428,7 @@ function makeDeepPartial(options, longs) {
427
428
  { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P> ${maybeExcludeType}>]: never };
428
429
  `);
429
430
  // Based on the type from ts-essentials
430
- const keys = addTypeToMessages(options) ? (0, ts_poet_1.code) `Exclude<keyof T, '$type'>` : (0, ts_poet_1.code) `keyof T`;
431
+ const keys = (0, options_1.addTypeToMessages)(options) ? (0, ts_poet_1.code) `Exclude<keyof T, '$type'>` : (0, ts_poet_1.code) `keyof T`;
431
432
  const DeepPartial = (0, ts_poet_1.conditionalOutput)("DeepPartial", (0, ts_poet_1.code) `
432
433
  ${maybeExport} type DeepPartial<T> = T extends ${Builtin}
433
434
  ? T
@@ -487,7 +488,7 @@ function makeTimestampMethods(options, longs) {
487
488
  // (note this only occurs when the string is > 8 characters)
488
489
  seconds = "Math.trunc(date.getTime() / 1_000).toString()";
489
490
  }
490
- const maybeTypeField = addTypeToMessages(options) ? `$type: 'google.protobuf.Timestamp',` : "";
491
+ const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: 'google.protobuf.Timestamp',` : "";
491
492
  const toTimestamp = (0, ts_poet_1.conditionalOutput)("toTimestamp", options.useDate === options_1.DateOption.STRING
492
493
  ? (0, ts_poet_1.code) `
493
494
  function toTimestamp(dateStr: string): ${Timestamp} {
@@ -589,6 +590,14 @@ function makeExtensionClass(options) {
589
590
  `);
590
591
  return { Extension };
591
592
  }
593
+ function makeAssertionUtils() {
594
+ const fail = (0, ts_poet_1.conditionalOutput)("fail", (0, ts_poet_1.code) `
595
+ function fail(message?: string): never {
596
+ throw new Error(message ?? "Failed");
597
+ }
598
+ `);
599
+ return { fail };
600
+ }
592
601
  // Create the interface with properties
593
602
  function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fullTypeName) {
594
603
  var _a;
@@ -597,7 +606,7 @@ function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fu
597
606
  (0, utils_1.maybeAddComment)(sourceInfo, chunks, (_a = messageDesc.options) === null || _a === void 0 ? void 0 : _a.deprecated);
598
607
  // interface name should be defined to avoid import collisions
599
608
  chunks.push((0, ts_poet_1.code) `export interface ${(0, ts_poet_1.def)(fullName)} {`);
600
- if (addTypeToMessages(options)) {
609
+ if ((0, options_1.addTypeToMessages)(options)) {
601
610
  chunks.push((0, ts_poet_1.code) `$type: '${fullTypeName}',`);
602
611
  }
603
612
  // When oneof=unions, we generate a single property with an ADT per `oneof` clause.
@@ -615,12 +624,15 @@ function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fu
615
624
  const info = sourceInfo.lookup(sourceInfo_1.Fields.message.field, index);
616
625
  (0, utils_1.maybeAddComment)(info, chunks, (_a = fieldDesc.options) === null || _a === void 0 ? void 0 : _a.deprecated);
617
626
  const name = (0, case_1.maybeSnakeToCamel)(fieldDesc.name, options);
618
- const type = (0, types_1.toTypeName)(ctx, messageDesc, fieldDesc);
619
- const q = (0, types_1.isOptionalProperty)(fieldDesc, messageDesc.options, options) ? "?" : "";
620
- chunks.push((0, ts_poet_1.code) `${maybeReadonly(options)}${name}${q}: ${type}, `);
627
+ const isOptional = (0, types_1.isOptionalProperty)(fieldDesc, messageDesc.options, options);
628
+ const type = (0, types_1.toTypeName)(ctx, messageDesc, fieldDesc, isOptional);
629
+ if (isOptional && !type.toString().includes("undefined")) {
630
+ console.warn(name, type);
631
+ }
632
+ chunks.push((0, ts_poet_1.code) `${maybeReadonly(options)}${name}${isOptional ? "?" : ""}: ${type}, `);
621
633
  });
622
634
  if (ctx.options.unknownFields) {
623
- chunks.push((0, ts_poet_1.code) `_unknownFields?: {[key: number]: Uint8Array[]},`);
635
+ chunks.push((0, ts_poet_1.code) `_unknownFields?: {[key: number]: Uint8Array[]} | undefined,`);
624
636
  }
625
637
  chunks.push((0, ts_poet_1.code) `}`);
626
638
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
@@ -635,7 +647,7 @@ function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
635
647
  return (0, ts_poet_1.code) `{ ${mbReadonly}$case: '${fieldName}', ${mbReadonly}${fieldName}: ${typeName} }`;
636
648
  }), { on: " | " });
637
649
  const name = (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, options);
638
- return (0, ts_poet_1.code) `${mbReadonly}${name}?: ${unionType},`;
650
+ return (0, ts_poet_1.code) `${mbReadonly}${name}?: ${unionType} | undefined,`;
639
651
  /*
640
652
  // Ideally we'd put the comments for each oneof field next to the anonymous
641
653
  // type we've created in the type union above, but ts-poet currently lacks
@@ -688,7 +700,7 @@ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
688
700
  : (0, types_1.defaultValue)(ctx, field);
689
701
  fields.push((0, ts_poet_1.code) `${name}: ${val}`);
690
702
  }
691
- if (addTypeToMessages(options)) {
703
+ if ((0, options_1.addTypeToMessages)(options)) {
692
704
  fields.unshift((0, ts_poet_1.code) `$type: '${fullTypeName}'`);
693
705
  }
694
706
  if (ctx.options.unknownFields && ctx.options.initializeFieldsAsUndefined) {
@@ -954,7 +966,7 @@ function getEncodeWriteSnippet(ctx, field) {
954
966
  return (place) => (0, ts_poet_1.code) `${type}.encode(${utils.toTimestamp}(${place}), writer.uint32(${tag}).fork()).ldelim()`;
955
967
  }
956
968
  else if ((0, types_1.isValueType)(ctx, field)) {
957
- const maybeTypeField = addTypeToMessages(options) ? `$type: '${field.typeName.slice(1)}',` : "";
969
+ const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: '${field.typeName.slice(1)}',` : "";
958
970
  const type = (0, types_1.basicTypeName)(ctx, field, { keepValueType: true });
959
971
  const wrappedValue = (place) => {
960
972
  if ((0, types_1.isAnyValueType)(field) || (0, types_1.isListValueType)(field) || (0, types_1.isStructType)(field) || (0, types_1.isFieldMaskType)(field)) {
@@ -1003,7 +1015,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1003
1015
  if ((0, types_1.isRepeated)(field)) {
1004
1016
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1005
1017
  const valueType = typeMap.get(field.typeName)[2].field[1];
1006
- const maybeTypeField = addTypeToMessages(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1018
+ const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1007
1019
  const entryWriteSnippet = (0, types_1.isValueType)(ctx, valueType)
1008
1020
  ? (0, ts_poet_1.code) `
1009
1021
  if (value !== undefined) {
@@ -1135,8 +1147,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1135
1147
  });
1136
1148
  if (options.unknownFields) {
1137
1149
  chunks.push((0, ts_poet_1.code) `if (message._unknownFields !== undefined) {
1138
- for (const key in message._unknownFields) {
1139
- const values = message._unknownFields[key];
1150
+ for (const [key, values] of Object.entries(message._unknownFields)) {
1140
1151
  const tag = parseInt(key, 10);
1141
1152
  for (const value of values) {
1142
1153
  writer.uint32(tag);
@@ -1255,7 +1266,7 @@ function generateExtension(ctx, message, extension) {
1255
1266
  return (place) => (0, ts_poet_1.code) `${type}.encode(${utils.toTimestamp}(${place}), writer.fork()).ldelim()`;
1256
1267
  }
1257
1268
  else if ((0, types_1.isValueType)(ctx, field)) {
1258
- const maybeTypeField = addTypeToMessages(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1269
+ const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1259
1270
  const type = (0, types_1.basicTypeName)(ctx, field, { keepValueType: true });
1260
1271
  const wrappedValue = (place) => {
1261
1272
  if ((0, types_1.isAnyValueType)(field) || (0, types_1.isListValueType)(field) || (0, types_1.isStructType)(field) || (0, types_1.isFieldMaskType)(field)) {
@@ -1330,7 +1341,7 @@ function generateExtension(ctx, message, extension) {
1330
1341
  chunks.push((0, ts_poet_1.code) `const values: ${type} = [];`);
1331
1342
  // start loop over all buffers
1332
1343
  chunks.push((0, ts_poet_1.code) `
1333
- for (var buffer of input) {
1344
+ for (const buffer of input) {
1334
1345
  const reader = ${Reader}.create(buffer);
1335
1346
  `);
1336
1347
  if ((0, types_1.packedType)(extension.type) === undefined) {
@@ -1360,7 +1371,7 @@ function generateExtension(ctx, message, extension) {
1360
1371
  else {
1361
1372
  // pick the last entry, since it overrides all previous entries if not repeated
1362
1373
  chunks.push((0, ts_poet_1.code) `
1363
- const reader = ${Reader}.create(input[input.length -1]);
1374
+ const reader = ${Reader}.create(input[input.length -1] ?? ${ctx.utils.fail}());
1364
1375
  return ${readSnippet};
1365
1376
  },
1366
1377
  `);
@@ -1387,7 +1398,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1387
1398
  fromJSON(${messageDesc.field.length > 0 ? "object" : "_"}: any): ${fullName} {
1388
1399
  return {
1389
1400
  `);
1390
- if (addTypeToMessages(options)) {
1401
+ if ((0, options_1.addTypeToMessages)(options)) {
1391
1402
  chunks.push((0, ts_poet_1.code) `$type: ${fullName}.$type,`);
1392
1403
  }
1393
1404
  const oneofFieldsCases = messageDesc.oneofDecl.map((oneof, oneofIndex) => messageDesc.field.filter(types_1.isWithinOneOf).filter((field) => field.oneofIndex === oneofIndex));
@@ -2014,6 +2025,3 @@ function maybeReadonly(options) {
2014
2025
  function maybeAsAny(options) {
2015
2026
  return options.useReadonlyTypes ? " as any" : "";
2016
2027
  }
2017
- function addTypeToMessages(options) {
2018
- return ((options.outputTypeAnnotations || options.outputTypeRegistry) && options.outputTypeAnnotations !== "static-only");
2019
- }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTsPoetOpts = exports.optionsFromParameter = exports.defaultOptions = exports.ServiceOption = exports.OneofOption = exports.EnvOption = exports.DateOption = exports.LongOption = void 0;
3
+ exports.addTypeToMessages = exports.getTsPoetOpts = exports.optionsFromParameter = exports.defaultOptions = exports.ServiceOption = exports.OneofOption = exports.EnvOption = exports.DateOption = exports.LongOption = void 0;
4
4
  var LongOption;
5
5
  (function (LongOption) {
6
6
  LongOption["NUMBER"] = "number";
@@ -211,3 +211,7 @@ function getTsPoetOpts(_options) {
211
211
  };
212
212
  }
213
213
  exports.getTsPoetOpts = getTsPoetOpts;
214
+ function addTypeToMessages(options) {
215
+ return ((options.outputTypeAnnotations || options.outputTypeRegistry) && options.outputTypeAnnotations !== "static-only");
216
+ }
217
+ exports.addTypeToMessages = addTypeToMessages;
@@ -531,19 +531,15 @@ function longTypeName(ctx) {
531
531
  }
532
532
  /** Maps `.some_proto_namespace.Message` to a TypeName. */
533
533
  function messageToTypeName(ctx, protoType, typeOptions = {}) {
534
+ var _a;
534
535
  const { options, typeMap } = ctx;
535
536
  // Watch for the wrapper types `.google.protobuf.*Value`. If we're mapping
536
537
  // them to basic built-in types, we union the type with undefined to
537
538
  // indicate the value is optional. Exceptions:
538
539
  // - If the field is repeated, values cannot be undefined.
539
- // - If useOptionals='messages' or useOptionals='all', all non-scalar types
540
- // are already optional properties, so there's no need for that union.
541
540
  let valueType = valueTypeName(ctx, protoType);
542
541
  if (!typeOptions.keepValueType && valueType) {
543
- if (!!typeOptions.repeated ||
544
- options.useOptionals === true ||
545
- options.useOptionals === "messages" ||
546
- options.useOptionals === "all") {
542
+ if ((_a = typeOptions.repeated) !== null && _a !== void 0 ? _a : false) {
547
543
  return valueType;
548
544
  }
549
545
  return (0, ts_poet_1.code) `${valueType} | undefined`;
@@ -575,26 +571,32 @@ function getEnumMethod(ctx, enumProtoType, methodSuffix) {
575
571
  }
576
572
  exports.getEnumMethod = getEnumMethod;
577
573
  /** Return the TypeName for any field (primitive/message/etc.) as exposed in the interface. */
578
- function toTypeName(ctx, messageDesc, field) {
574
+ function toTypeName(ctx, messageDesc, field, ensureOptional = false) {
575
+ function finalize(type, isOptional) {
576
+ if (isOptional) {
577
+ return (0, ts_poet_1.code) `${type} | undefined`;
578
+ }
579
+ return type;
580
+ }
579
581
  let type = basicTypeName(ctx, field, { keepValueType: false });
580
582
  if (isRepeated(field)) {
581
583
  const mapType = messageDesc ? detectMapType(ctx, messageDesc, field) : false;
582
584
  if (mapType) {
583
585
  const { keyType, valueType } = mapType;
584
586
  if (ctx.options.useMapType) {
585
- return (0, ts_poet_1.code) `Map<${keyType}, ${valueType}>`;
587
+ return finalize((0, ts_poet_1.code) `Map<${keyType}, ${valueType}>`, ensureOptional);
586
588
  }
587
- return (0, ts_poet_1.code) `{ [key: ${keyType} ]: ${valueType} }`;
589
+ return finalize((0, ts_poet_1.code) `{ [key: ${keyType} ]: ${valueType} }`, ensureOptional);
588
590
  }
589
591
  if (ctx.options.useReadonlyTypes) {
590
- return (0, ts_poet_1.code) `readonly ${type}[]`;
592
+ return finalize((0, ts_poet_1.code) `readonly ${type}[]`, ensureOptional);
591
593
  }
592
- return (0, ts_poet_1.code) `${type}[]`;
594
+ return finalize((0, ts_poet_1.code) `${type}[]`, ensureOptional);
593
595
  }
594
596
  if (isValueType(ctx, field)) {
595
597
  // google.protobuf.*Value types are already unioned with `undefined`
596
598
  // in messageToTypeName, so no need to consider them for that here.
597
- return type;
599
+ return finalize(type, false);
598
600
  }
599
601
  // By default (useOptionals='none', oneof=properties), non-scalar fields
600
602
  // outside oneofs and all fields within a oneof clause need to be unioned
@@ -608,14 +610,12 @@ function toTypeName(ctx, messageDesc, field) {
608
610
  // clause, spelling each option out inside a large type union. No need for
609
611
  // union with `undefined` here, either.
610
612
  const { options } = ctx;
611
- if ((!isWithinOneOf(field) &&
613
+ return finalize(type, (!isWithinOneOf(field) &&
612
614
  isMessage(field) &&
613
615
  (options.useOptionals === false || options.useOptionals === "none")) ||
614
616
  (isWithinOneOf(field) && options.oneof === options_1.OneofOption.PROPERTIES) ||
615
- (isWithinOneOf(field) && field.proto3Optional)) {
616
- return (0, ts_poet_1.code) `${type} | undefined`;
617
- }
618
- return type;
617
+ (isWithinOneOf(field) && field.proto3Optional) ||
618
+ ensureOptional);
619
619
  }
620
620
  exports.toTypeName = toTypeName;
621
621
  function detectMapType(ctx, messageDesc, fieldDesc) {
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const case_1 = require("../src/case");
4
+ const options_1 = require("../src/options");
5
+ const utils_1 = require("../src/utils");
6
+ const keys = (0, options_1.optionsFromParameter)("snakeToCamel=keys");
7
+ describe("case", () => {
8
+ it("converts snake to camel by default", () => {
9
+ expect((0, case_1.maybeSnakeToCamel)("foo_bar", (0, options_1.optionsFromParameter)(undefined))).toEqual("fooBar");
10
+ });
11
+ it("leaves as-is if snakeToCamel is false", () => {
12
+ expect((0, case_1.maybeSnakeToCamel)("foo_bar", (0, options_1.optionsFromParameter)("snakeToCamel=false"))).toEqual("foo_bar");
13
+ });
14
+ it("de-upper cases", () => {
15
+ expect((0, case_1.maybeSnakeToCamel)("FOO_BAR", keys)).toEqual("fooBar");
16
+ });
17
+ it("leaves existing mixed cases", () => {
18
+ expect((0, case_1.maybeSnakeToCamel)("clientI_d", keys)).toEqual("clientID");
19
+ expect((0, case_1.maybeSnakeToCamel)("menu_calendarI_d", keys)).toEqual("menuCalendarID");
20
+ expect((0, case_1.maybeSnakeToCamel)("display_nameI18n", keys)).toEqual("displayNameI18n");
21
+ });
22
+ it("leaves the first character as it was", () => {
23
+ expect((0, case_1.maybeSnakeToCamel)("Foo_Bar", keys)).toEqual("FooBar");
24
+ });
25
+ it("does nothing is already camel", () => {
26
+ expect((0, case_1.maybeSnakeToCamel)("FooBar", keys)).toEqual("FooBar");
27
+ });
28
+ // deal with original protoc which converts
29
+ // _uuid -> Uuid
30
+ // __uuid -> Uuid
31
+ // _uuid_foo -> UuidFoo
32
+ it("converts snake to camel with first underscore", () => {
33
+ expect((0, case_1.maybeSnakeToCamel)("_uuid", { snakeToCamel: ["keys"] })).toEqual("Uuid");
34
+ });
35
+ it("converts snake to camel with first double underscore", () => {
36
+ expect((0, case_1.maybeSnakeToCamel)("__uuid", { snakeToCamel: ["keys"] })).toEqual("Uuid");
37
+ });
38
+ it("converts snake to camel with first underscore and camelize other", () => {
39
+ expect((0, case_1.maybeSnakeToCamel)("_uuid_foo", { snakeToCamel: ["keys"] })).toEqual("UuidFoo");
40
+ });
41
+ it("converts string to camel case respecting word separation, getAPIValue === getApiValue", () => {
42
+ expect((0, case_1.camelCaseGrpc)("GetAPIValue")).toEqual("getApiValue");
43
+ });
44
+ describe("getFieldJsonName", () => {
45
+ it("keeps snake case when jsonName is probably not set", () => {
46
+ expect((0, utils_1.getFieldJsonName)({ name: "foo_bar", jsonName: "fooBar" }, { snakeToCamel: [] })).toBe("foo_bar");
47
+ });
48
+ it("uses jsonName when it is set", () => {
49
+ expect((0, utils_1.getFieldJsonName)({ name: "foo_bar", jsonName: "foo" }, { snakeToCamel: [] })).toBe("foo");
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const options_1 = require("../src/options");
4
+ describe("options", () => {
5
+ it("can set outputJsonMethods with nestJs=true", () => {
6
+ expect((0, options_1.optionsFromParameter)("nestJs=true,outputJsonMethods=true")).toMatchInlineSnapshot(`
7
+ {
8
+ "M": {},
9
+ "addGrpcMetadata": false,
10
+ "addNestjsRestParameter": false,
11
+ "constEnums": false,
12
+ "context": false,
13
+ "emitImportedFiles": true,
14
+ "enumsAsLiterals": false,
15
+ "env": "both",
16
+ "esModuleInterop": false,
17
+ "exportCommonSymbols": true,
18
+ "fileSuffix": "",
19
+ "forceLong": "number",
20
+ "importSuffix": "",
21
+ "initializeFieldsAsUndefined": false,
22
+ "lowerCaseServiceMethods": true,
23
+ "metadataType": undefined,
24
+ "nestJs": true,
25
+ "oneof": "properties",
26
+ "onlyTypes": false,
27
+ "outputClientImpl": false,
28
+ "outputEncodeMethods": false,
29
+ "outputExtensions": false,
30
+ "outputIndex": false,
31
+ "outputJsonMethods": true,
32
+ "outputPartialMethods": false,
33
+ "outputSchema": false,
34
+ "outputServices": [
35
+ "default",
36
+ ],
37
+ "outputTypeAnnotations": false,
38
+ "outputTypeRegistry": false,
39
+ "removeEnumPrefix": false,
40
+ "returnObservable": false,
41
+ "snakeToCamel": [
42
+ "json",
43
+ "keys",
44
+ ],
45
+ "stringEnums": false,
46
+ "unknownFields": false,
47
+ "unrecognizedEnum": true,
48
+ "useAbortSignal": false,
49
+ "useAsyncIterable": false,
50
+ "useDate": "timestamp",
51
+ "useExactTypes": true,
52
+ "useJsonWireFormat": false,
53
+ "useMapType": false,
54
+ "useMongoObjectId": false,
55
+ "useNumericEnumForJson": false,
56
+ "useOptionals": "none",
57
+ "usePrototypeForDefaults": false,
58
+ "useReadonlyTypes": false,
59
+ "useSnakeTypeName": true,
60
+ }
61
+ `);
62
+ });
63
+ it("can set outputJsonMethods with nestJs=true", () => {
64
+ const options = (0, options_1.optionsFromParameter)("outputClientImpl=grpc-web,addGrpcMetadata=false");
65
+ expect(options).toMatchObject({
66
+ outputClientImpl: "grpc-web",
67
+ addGrpcMetadata: false,
68
+ });
69
+ });
70
+ it("can set fileSuffix", () => {
71
+ const options = (0, options_1.optionsFromParameter)("fileSuffix=.pb");
72
+ expect(options).toMatchObject({
73
+ fileSuffix: ".pb",
74
+ });
75
+ });
76
+ it("can set outputServices to false", () => {
77
+ const options = (0, options_1.optionsFromParameter)("outputServices=false");
78
+ expect(options).toMatchObject({
79
+ outputServices: [options_1.ServiceOption.NONE],
80
+ });
81
+ });
82
+ it("can set outputServices to grpc", () => {
83
+ const options = (0, options_1.optionsFromParameter)("outputServices=grpc-js");
84
+ expect(options).toMatchObject({
85
+ outputServices: [options_1.ServiceOption.GRPC],
86
+ });
87
+ });
88
+ it("can set useOptionals to boolean", () => {
89
+ const options = (0, options_1.optionsFromParameter)("useOptionals=true");
90
+ expect(options).toMatchObject({
91
+ useOptionals: true,
92
+ });
93
+ });
94
+ it("can set useOptionals to string", () => {
95
+ const options = (0, options_1.optionsFromParameter)("useOptionals=messages");
96
+ expect(options).toMatchObject({
97
+ useOptionals: "messages",
98
+ });
99
+ });
100
+ it("can set snakeToCamel as string", () => {
101
+ const options = (0, options_1.optionsFromParameter)("snakeToCamel=keys");
102
+ expect(options).toMatchObject({ snakeToCamel: ["keys"] });
103
+ });
104
+ it("can set snakeToCamel as two values", () => {
105
+ const options = (0, options_1.optionsFromParameter)("snakeToCamel=keys_json");
106
+ expect(options).toMatchObject({ snakeToCamel: ["keys", "json"] });
107
+ });
108
+ it("can set multiple values as an array", () => {
109
+ const options = (0, options_1.optionsFromParameter)("foo=one,foo=two");
110
+ expect(options).toMatchObject({ foo: ["one", "two"] });
111
+ });
112
+ it("´output*=false implies onlyTypes", () => {
113
+ const options = (0, options_1.optionsFromParameter)("outputJsonMethods=false,outputEncodeMethods=false,outputClientImpl=false");
114
+ expect(options).toMatchObject({
115
+ onlyTypes: true,
116
+ });
117
+ });
118
+ it("onlyTypes implies output*=false", () => {
119
+ const options = (0, options_1.optionsFromParameter)("onlyTypes=true");
120
+ expect(options).toMatchObject({
121
+ outputJsonMethods: false,
122
+ outputEncodeMethods: false,
123
+ outputClientImpl: false,
124
+ nestJs: false,
125
+ });
126
+ });
127
+ it("useJsonWireFormat requires onlyTypes", () => {
128
+ const options = (0, options_1.optionsFromParameter)("useJsonWireFormat=true");
129
+ expect(options).toMatchObject({
130
+ useJsonWireFormat: false,
131
+ });
132
+ });
133
+ it("useSnakeTypeName", () => {
134
+ const options = (0, options_1.optionsFromParameter)("useSnakeTypeName=false");
135
+ expect(options).toMatchObject({
136
+ useSnakeTypeName: false,
137
+ });
138
+ });
139
+ it("useJsonWireFormat implies useDate=string and stringEnums=true", () => {
140
+ const options = (0, options_1.optionsFromParameter)("useJsonWireFormat=true,onlyTypes=true");
141
+ expect(options).toMatchObject({
142
+ useJsonWireFormat: true,
143
+ onlyTypes: true,
144
+ stringEnums: true,
145
+ useDate: options_1.DateOption.STRING,
146
+ });
147
+ });
148
+ });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const options_1 = require("../src/options");
4
+ const types_1 = require("../src/types");
5
+ const ts_poet_1 = require("ts-poet");
6
+ const fakeProto = undefined;
7
+ describe("types", () => {
8
+ describe("messageToTypeName", () => {
9
+ const testCases = [
10
+ {
11
+ descr: "top-level messages",
12
+ typeMap: new Map([[".namespace.Message", ["namespace", "Message", fakeProto]]]),
13
+ protoType: ".namespace.Message",
14
+ expected: (0, ts_poet_1.code) `${(0, ts_poet_1.imp)("Message@./namespace")}`,
15
+ },
16
+ {
17
+ descr: "nested messages",
18
+ typeMap: new Map([[".namespace.Message.Inner", ["namespace", "Message_Inner", fakeProto]]]),
19
+ protoType: ".namespace.Message.Inner",
20
+ expected: (0, ts_poet_1.code) `${(0, ts_poet_1.imp)("Message_Inner@./namespace")}`,
21
+ },
22
+ {
23
+ descr: "nested messages: .js import suffix",
24
+ typeMap: new Map([[".namespace.Message.Inner", ["namespace", "Message_Inner", fakeProto]]]),
25
+ protoType: ".namespace.Message.Inner",
26
+ options: { ...(0, options_1.defaultOptions)(), importSuffix: ".js" },
27
+ expected: (0, ts_poet_1.code) `${(0, ts_poet_1.imp)("Message_Inner@./namespace.js")}`,
28
+ },
29
+ {
30
+ descr: "value types",
31
+ typeMap: new Map(),
32
+ protoType: ".google.protobuf.StringValue",
33
+ expected: (0, ts_poet_1.code) `string | undefined`,
34
+ },
35
+ {
36
+ descr: "value types (useOptionals=true)",
37
+ typeMap: new Map(),
38
+ protoType: ".google.protobuf.StringValue",
39
+ options: { ...(0, options_1.defaultOptions)(), useOptionals: true },
40
+ expected: (0, ts_poet_1.code) `string | undefined`,
41
+ },
42
+ {
43
+ descr: 'value types (useOptionals="all")',
44
+ typeMap: new Map(),
45
+ protoType: ".google.protobuf.StringValue",
46
+ options: { ...(0, options_1.defaultOptions)(), useOptionals: "all" },
47
+ expected: (0, ts_poet_1.code) `string | undefined`,
48
+ },
49
+ ];
50
+ testCases.forEach((t) => it(t.descr, async () => {
51
+ const ctx = { options: (0, options_1.defaultOptions)(), utils: undefined, ...t };
52
+ const got = (0, types_1.messageToTypeName)(ctx, t.protoType);
53
+ expect(got.toString()).toEqual(t.expected.toString());
54
+ }));
55
+ });
56
+ });
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../src/utils");
4
+ const ts_poet_1 = require("ts-poet");
5
+ describe("utils", () => {
6
+ describe("maybeAddComment", () => {
7
+ it("handles single-line impl comments", () => {
8
+ // Foo
9
+ const chunks = [];
10
+ (0, utils_1.maybeAddComment)({ leadingComments: " Foo\n" }, chunks);
11
+ expect((0, ts_poet_1.joinCode)(chunks).toString()).toMatchInlineSnapshot(`
12
+ "/** Foo */
13
+ "
14
+ `);
15
+ });
16
+ it("handles single-dot star comments", () => {
17
+ // /* Foo */
18
+ const chunks = [];
19
+ (0, utils_1.maybeAddComment)({ leadingComments: " Foo " }, chunks);
20
+ expect((0, ts_poet_1.joinCode)(chunks).toString()).toMatchInlineSnapshot(`
21
+ "/** Foo */
22
+ "
23
+ `);
24
+ });
25
+ it("handles single-line double-dot star comments", () => {
26
+ // /** Foo */
27
+ const chunks = [];
28
+ (0, utils_1.maybeAddComment)({ leadingComments: " * Foo " }, chunks);
29
+ expect((0, ts_poet_1.joinCode)(chunks).toString()).toMatchInlineSnapshot(`
30
+ "/** Foo */
31
+ "
32
+ `);
33
+ });
34
+ it("handles double-line double-dot star comments", () => {
35
+ // /**
36
+ // * Foo
37
+ // *
38
+ // * bar.
39
+ // */
40
+ const chunks = [];
41
+ (0, utils_1.maybeAddComment)({ leadingComments: "*\n Foo\n \n bar.\n" }, chunks);
42
+ expect((0, ts_poet_1.joinCode)(chunks).toString()).toMatchInlineSnapshot(`
43
+ "/**
44
+ * Foo
45
+ *
46
+ * bar.
47
+ */
48
+ "
49
+ `);
50
+ });
51
+ it("handles double-line impl comments", () => {
52
+ // // Foo
53
+ // // Bar
54
+ const chunks = [];
55
+ (0, utils_1.maybeAddComment)({ leadingComments: " Foo\n Bar\n" }, chunks);
56
+ expect((0, ts_poet_1.joinCode)(chunks).toString()).toMatchInlineSnapshot(`
57
+ "/**
58
+ * Foo
59
+ * Bar
60
+ */
61
+ "
62
+ `);
63
+ });
64
+ });
65
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.151.0",
3
+ "version": "1.152.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",
@@ -16,6 +16,7 @@
16
16
  "proto2pbjs": "docker compose run --rm protoc pbjs.sh",
17
17
  "bin2ts": "docker compose run --rm protoc codegen.sh",
18
18
  "test": "yarn jest -c jest.config.js --maxWorkers=2",
19
+ "tsc:check": "./tsc-check.sh tsconfig.json integration/tsconfig.json integration/tsconfig.proto.json protos/tsconfig.json",
19
20
  "format": "prettier --write {src,tests}/**/*.ts integration/*.ts",
20
21
  "format:check": "prettier --list-different {src,tests}/**/*.ts",
21
22
  "setup:docker": "docker compose build",
@@ -42,8 +43,9 @@
42
43
  "@semantic-release/github": "^9.0.2",
43
44
  "@semantic-release/npm": "^10.0.3",
44
45
  "@semantic-release/release-notes-generator": "^11.0.2",
46
+ "@tsconfig/strictest": "^2.0.1",
45
47
  "@types/jest": "^29.5.2",
46
- "@types/node": "^14.14.37",
48
+ "@types/node": "^16.18.38",
47
49
  "chokidar": "^3.5.3",
48
50
  "jest": "^29.5.0",
49
51
  "jest-ts-webcompat-resolver": "^1.0.0",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes