ts-proto 1.94.0 → 1.96.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [1.96.1](https://github.com/stephenh/ts-proto/compare/v1.96.0...v1.96.1) (2021-12-28)
2
+
3
+
4
+ ### Performance Improvements
5
+
6
+ * optimize object creation in `decode`, `fromJSON` and `fromPartial` ([#457](https://github.com/stephenh/ts-proto/issues/457)) ([70832d3](https://github.com/stephenh/ts-proto/commit/70832d33bae82ecb3c5f87845d14e992a13437e4))
7
+
8
+ # [1.96.0](https://github.com/stephenh/ts-proto/compare/v1.95.1...v1.96.0) (2021-12-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * `enumsAsLiterals` option ([#450](https://github.com/stephenh/ts-proto/issues/450)) ([fcaade2](https://github.com/stephenh/ts-proto/commit/fcaade2855ae28ea3553a365556ccb92a9644d70))
14
+
15
+ ## [1.95.1](https://github.com/stephenh/ts-proto/compare/v1.95.0...v1.95.1) (2021-12-23)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Add service to the client constructor. ([#455](https://github.com/stephenh/ts-proto/issues/455)) ([8c32104](https://github.com/stephenh/ts-proto/commit/8c32104a8522cfe2febcf2338d51710021d837ff))
21
+
22
+ # [1.95.0](https://github.com/stephenh/ts-proto/compare/v1.94.0...v1.95.0) (2021-12-14)
23
+
24
+
25
+ ### Features
26
+
27
+ * Add useOptionals=all to enable non-field members to be optional. ([#402](https://github.com/stephenh/ts-proto/issues/402)) ([e7b70cb](https://github.com/stephenh/ts-proto/commit/e7b70cbd7b9bd43bf9e6e54e25bc48c527718317))
28
+
1
29
  # [1.94.0](https://github.com/stephenh/ts-proto/compare/v1.93.3...v1.94.0) (2021-12-14)
2
30
 
3
31
 
package/README.markdown CHANGED
@@ -159,7 +159,7 @@ creating a class and calling the right getters/setters.
159
159
 
160
160
  (Configurable with the `useDate` parameter.)
161
161
 
162
- - `fromJSON`/`toJSON` support the [canonical Protobuf JS](https://developers.google.com/protocol-buffers/docs/proto3#json) format (i.e. timestamps are ISO strings)
162
+ - `fromJSON`/`toJSON` use the [proto3 canonical JSON encoding format](https://developers.google.com/protocol-buffers/docs/proto3#json) (e.g. timestamps are ISO strings), unlike [`protobufjs`](https://github.com/protobufjs/protobuf.js/issues/1304).
163
163
 
164
164
  # Auto-Batching / N+1 Prevention
165
165
 
@@ -348,6 +348,8 @@ Generated code will be placed in the Gradle build directory.
348
348
 
349
349
  - With `--ts_proto_opt=fileSuffix=<SUFFIX>`, ts-proto will emit generated files using the specified suffix. A `helloworld.proto` file with `fileSuffix=.pb` would be generated as `helloworld.pb.ts`. This is common behavior in other protoc plugins and provides a way to quickly glob all the generated files.
350
350
 
351
+ - With `--ts_proto_opt=enumsAsLiterals=true`, the generated enum types will be enum-ish object with `as const`.
352
+
351
353
  ### Only Types
352
354
 
353
355
  If you're looking for `ts-proto` to generate only types for your Protobuf types then passing all three of `outputEncodeMethods`, `outputJsonMethods`, and `outputClientImpl` as `false` is probably what you want, i.e.:
package/build/enums.js CHANGED
@@ -13,17 +13,30 @@ function generateEnum(ctx, fullName, enumDesc, sourceInfo) {
13
13
  const { options } = ctx;
14
14
  const chunks = [];
15
15
  utils_1.maybeAddComment(sourceInfo, chunks, (_a = enumDesc.options) === null || _a === void 0 ? void 0 : _a.deprecated);
16
- chunks.push(ts_poet_1.code `export ${options.constEnums ? 'const ' : ''}enum ${ts_poet_1.def(fullName)} {`);
16
+ if (options.enumsAsLiterals) {
17
+ chunks.push(ts_poet_1.code `export const ${ts_poet_1.def(fullName)} = {`);
18
+ }
19
+ else {
20
+ chunks.push(ts_poet_1.code `export ${options.constEnums ? 'const ' : ''}enum ${ts_poet_1.def(fullName)} {`);
21
+ }
22
+ const delimiter = options.enumsAsLiterals ? ':' : '=';
17
23
  enumDesc.value.forEach((valueDesc, index) => {
18
24
  var _a;
19
25
  const info = sourceInfo.lookup(sourceInfo_1.Fields.enum.value, index);
20
26
  utils_1.maybeAddComment(info, chunks, (_a = valueDesc.options) === null || _a === void 0 ? void 0 : _a.deprecated, `${valueDesc.name} - `);
21
- chunks.push(ts_poet_1.code `${valueDesc.name} = ${options.stringEnums ? `"${valueDesc.name}"` : valueDesc.number.toString()},`);
27
+ chunks.push(ts_poet_1.code `${valueDesc.name} ${delimiter} ${options.stringEnums ? `"${valueDesc.name}"` : valueDesc.number.toString()},`);
22
28
  });
23
29
  if (options.unrecognizedEnum)
24
30
  chunks.push(ts_poet_1.code `
25
- ${UNRECOGNIZED_ENUM_NAME} = ${options.stringEnums ? `"${UNRECOGNIZED_ENUM_NAME}"` : UNRECOGNIZED_ENUM_VALUE.toString()},`);
26
- chunks.push(ts_poet_1.code `}`);
31
+ ${UNRECOGNIZED_ENUM_NAME} ${delimiter} ${options.stringEnums ? `"${UNRECOGNIZED_ENUM_NAME}"` : UNRECOGNIZED_ENUM_VALUE.toString()},`);
32
+ if (options.enumsAsLiterals) {
33
+ chunks.push(ts_poet_1.code `} as const`);
34
+ chunks.push(ts_poet_1.code `\n`);
35
+ chunks.push(ts_poet_1.code `export type ${ts_poet_1.def(fullName)} = typeof ${ts_poet_1.def(fullName)}[keyof typeof ${ts_poet_1.def(fullName)}]`);
36
+ }
37
+ else {
38
+ chunks.push(ts_poet_1.code `}`);
39
+ }
27
40
  if (options.outputJsonMethods || (options.stringEnums && options.outputEncodeMethods)) {
28
41
  chunks.push(ts_poet_1.code `\n`);
29
42
  chunks.push(generateEnumFromJson(ctx, fullName, enumDesc));
@@ -192,6 +192,7 @@ function generateClientConstructor(fileDesc, serviceDesc) {
192
192
  credentials: ${ChannelCredentials},
193
193
  options?: Partial<${ChannelOptions}>,
194
194
  ): ${serviceDesc.name}Client;
195
+ service: typeof ${serviceDesc.name}Service;
195
196
  }
196
197
  `;
197
198
  }
package/build/main.js CHANGED
@@ -19,6 +19,14 @@ const generate_generic_service_definition_1 = require("./generate-generic-servic
19
19
  function generateFile(ctx, fileDesc) {
20
20
  var _a;
21
21
  const { options, utils } = ctx;
22
+ if (options.useOptionals === false) {
23
+ console.warn("ts-proto: Passing useOptionals as a boolean option is deprecated and will be removed in a future version. Please pass the string 'none' instead of false.");
24
+ options.useOptionals = 'none';
25
+ }
26
+ else if (options.useOptionals === true) {
27
+ console.warn("ts-proto: Passing useOptionals as a boolean option is deprecated and will be removed in a future version. Please pass the string 'messages' instead of true.");
28
+ options.useOptionals = 'messages';
29
+ }
22
30
  // Google's protofiles are organized like Java, where package == the folder the file
23
31
  // is in, and file == a specific service within the package. I.e. you can have multiple
24
32
  // company/foo.proto and company/bar.proto files, where package would be 'company'.
@@ -62,7 +70,7 @@ function generateFile(ctx, fileDesc) {
62
70
  // then add the encoder/decoder/base instance
63
71
  visit_1.visit(fileDesc, sourceInfo, (fullName, message, sInfo, fullProtoTypeName) => {
64
72
  const fullTypeName = utils_1.maybePrefixPackage(fileDesc, fullProtoTypeName);
65
- chunks.push(generateBaseInstance(ctx, fullName, message, fullTypeName));
73
+ chunks.push(generateBaseInstanceFactory(ctx, fullName, message, fullTypeName));
66
74
  const staticMembers = [];
67
75
  if (options.outputTypeRegistry) {
68
76
  staticMembers.push(ts_poet_1.code `$type: '${fullTypeName}' as const`);
@@ -388,10 +396,6 @@ function makeTimestampMethods(options, longs) {
388
396
  `);
389
397
  return { toTimestamp, fromTimestamp, fromJsonTimestamp };
390
398
  }
391
- // When useOptionals=true, non-scalar fields are translated into optional properties.
392
- function isOptionalProperty(field, options) {
393
- return (options.useOptionals && types_1.isMessage(field) && !types_1.isRepeated(field)) || field.proto3Optional;
394
- }
395
399
  // Create the interface with properties
396
400
  function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fullTypeName) {
397
401
  var _a;
@@ -419,7 +423,7 @@ function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fu
419
423
  utils_1.maybeAddComment(info, chunks, (_a = fieldDesc.options) === null || _a === void 0 ? void 0 : _a.deprecated);
420
424
  const name = case_1.maybeSnakeToCamel(fieldDesc.name, options);
421
425
  const type = types_1.toTypeName(ctx, messageDesc, fieldDesc);
422
- const q = isOptionalProperty(fieldDesc, options) ? '?' : '';
426
+ const q = types_1.isOptionalProperty(fieldDesc, messageDesc.options, options) ? '?' : '';
423
427
  chunks.push(ts_poet_1.code `${name}${q}: ${type}, `);
424
428
  });
425
429
  chunks.push(ts_poet_1.code `}`);
@@ -456,20 +460,39 @@ function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
456
460
  return prop;
457
461
  */
458
462
  }
459
- // Create a 'base' instance with default values for decode to use as a prototype
460
- function generateBaseInstance(ctx, fullName, messageDesc, fullTypeName) {
461
- const fields = messageDesc.field
462
- .filter((field) => !types_1.isWithinOneOf(field))
463
- .map((field) => [field, types_1.defaultValue(ctx, field)])
464
- .filter(([field, val]) => val !== 'undefined' && !types_1.isBytes(field))
465
- .map(([field, val]) => {
463
+ // Create a function that constructs 'base' instance with default values for decode to use as a prototype
464
+ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
465
+ const fields = [];
466
+ // When oneof=unions, we generate a single property with an ADT per `oneof` clause.
467
+ const processedOneofs = new Set();
468
+ for (const field of messageDesc.field) {
469
+ if (types_1.isWithinOneOfThatShouldBeUnion(ctx.options, field)) {
470
+ const { oneofIndex } = field;
471
+ if (!processedOneofs.has(oneofIndex)) {
472
+ processedOneofs.add(oneofIndex);
473
+ const name = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[oneofIndex].name, ctx.options);
474
+ fields.push(ts_poet_1.code `${name}: undefined`);
475
+ }
476
+ continue;
477
+ }
466
478
  const name = case_1.maybeSnakeToCamel(field.name, ctx.options);
467
- return ts_poet_1.code `${name}: ${val}`;
468
- });
479
+ const val = types_1.isWithinOneOf(field)
480
+ ? 'undefined'
481
+ : types_1.isMapType(ctx, messageDesc, field)
482
+ ? '{}'
483
+ : types_1.isRepeated(field)
484
+ ? '[]'
485
+ : types_1.defaultValue(ctx, field);
486
+ fields.push(ts_poet_1.code `${name}: ${val}`);
487
+ }
469
488
  if (ctx.options.outputTypeRegistry) {
470
489
  fields.unshift(ts_poet_1.code `$type: '${fullTypeName}'`);
471
490
  }
472
- return ts_poet_1.code `const base${fullName}: object = { ${ts_poet_1.joinCode(fields, { on: ',' })} };`;
491
+ return ts_poet_1.code `
492
+ function createBase${fullName}(): ${fullName} {
493
+ return { ${ts_poet_1.joinCode(fields, { on: ',' })} };
494
+ }
495
+ `;
473
496
  }
474
497
  /** Creates a function to decode a message by loop overing the tags. */
475
498
  function generateDecode(ctx, fullName, messageDesc) {
@@ -483,22 +506,8 @@ function generateDecode(ctx, fullName, messageDesc) {
483
506
  ): ${fullName} {
484
507
  const reader = input instanceof ${Reader} ? input : new ${Reader}(input);
485
508
  let end = length === undefined ? reader.len : reader.pos + length;
486
- const message = { ...base${fullName} } as ${fullName};
509
+ const message = createBase${fullName}();
487
510
  `);
488
- // initialize all lists
489
- messageDesc.field.filter(types_1.isRepeated).forEach((field) => {
490
- const name = case_1.maybeSnakeToCamel(field.name, options);
491
- const value = types_1.isMapType(ctx, messageDesc, field) ? '{}' : '[]';
492
- chunks.push(ts_poet_1.code `message.${name} = ${value};`);
493
- });
494
- // initialize all buffers
495
- messageDesc.field
496
- .filter((field) => !types_1.isRepeated(field) && !types_1.isWithinOneOf(field) && types_1.isBytes(field))
497
- .forEach((field) => {
498
- const value = options.env === options_1.EnvOption.NODE ? 'Buffer.alloc(0)' : 'new Uint8Array()';
499
- const name = case_1.maybeSnakeToCamel(field.name, options);
500
- chunks.push(ts_poet_1.code `message.${name} = ${value};`);
501
- });
502
511
  // start the tag loop
503
512
  chunks.push(ts_poet_1.code `
504
513
  while (reader.pos < end) {
@@ -563,28 +572,29 @@ function generateDecode(ctx, fullName, messageDesc) {
563
572
  }
564
573
  // and then use the snippet to handle repeated fields if necessary
565
574
  if (types_1.isRepeated(field)) {
575
+ const maybeNonNullAssertion = ctx.options.useOptionals === 'all' ? '!' : '';
566
576
  if (types_1.isMapType(ctx, messageDesc, field)) {
567
577
  // We need a unique const within the `cast` statement
568
578
  const varName = `entry${field.number}`;
569
579
  chunks.push(ts_poet_1.code `
570
580
  const ${varName} = ${readSnippet};
571
581
  if (${varName}.value !== undefined) {
572
- message.${fieldName}[${varName}.key] = ${varName}.value;
582
+ message.${fieldName}${maybeNonNullAssertion}[${varName}.key] = ${varName}.value;
573
583
  }
574
584
  `);
575
585
  }
576
586
  else if (types_1.packedType(field.type) === undefined) {
577
- chunks.push(ts_poet_1.code `message.${fieldName}.push(${readSnippet});`);
587
+ chunks.push(ts_poet_1.code `message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});`);
578
588
  }
579
589
  else {
580
590
  chunks.push(ts_poet_1.code `
581
591
  if ((tag & 7) === 2) {
582
592
  const end2 = reader.uint32() + reader.pos;
583
593
  while (reader.pos < end2) {
584
- message.${fieldName}.push(${readSnippet});
594
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
585
595
  }
586
596
  } else {
587
- message.${fieldName}.push(${readSnippet});
597
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
588
598
  }
589
599
  `);
590
600
  }
@@ -662,6 +672,7 @@ function generateEncode(ctx, fullName, messageDesc) {
662
672
  else {
663
673
  throw new Error(`Unhandled field ${field}`);
664
674
  }
675
+ const isOptional = types_1.isOptionalProperty(field, messageDesc.options, options);
665
676
  if (types_1.isRepeated(field)) {
666
677
  if (types_1.isMapType(ctx, messageDesc, field)) {
667
678
  const valueType = typeMap.get(field.typeName)[2].field[1];
@@ -673,18 +684,29 @@ function generateEncode(ctx, fullName, messageDesc) {
673
684
  }
674
685
  `
675
686
  : writeSnippet(`{ ${maybeTypeField} key: key as any, value }`);
687
+ const optionalAlternative = isOptional ? ' || {}' : '';
676
688
  chunks.push(ts_poet_1.code `
677
- Object.entries(message.${fieldName}).forEach(([key, value]) => {
689
+ Object.entries(message.${fieldName}${optionalAlternative}).forEach(([key, value]) => {
678
690
  ${entryWriteSnippet}
679
691
  });
680
692
  `);
681
693
  }
682
694
  else if (types_1.packedType(field.type) === undefined) {
683
- chunks.push(ts_poet_1.code `
695
+ const listWriteSnippet = ts_poet_1.code `
684
696
  for (const v of message.${fieldName}) {
685
697
  ${writeSnippet('v!')};
686
698
  }
687
- `);
699
+ `;
700
+ if (isOptional) {
701
+ chunks.push(ts_poet_1.code `
702
+ if (message.${fieldName} !== undefined && message.${fieldName}.length !== 0) {
703
+ ${listWriteSnippet}
704
+ }
705
+ `);
706
+ }
707
+ else {
708
+ chunks.push(listWriteSnippet);
709
+ }
688
710
  }
689
711
  else if (types_1.isEnum(field) && options.stringEnums) {
690
712
  // This is a lot like the `else` clause, but we wrap `fooToNumber` around it.
@@ -693,24 +715,44 @@ function generateEncode(ctx, fullName, messageDesc) {
693
715
  // (i.e. just one tag and multiple values).
694
716
  const tag = ((field.number << 3) | 2) >>> 0;
695
717
  const toNumber = types_1.getEnumMethod(ctx, field.typeName, 'ToNumber');
696
- chunks.push(ts_poet_1.code `
718
+ const listWriteSnippet = ts_poet_1.code `
697
719
  writer.uint32(${tag}).fork();
698
720
  for (const v of message.${fieldName}) {
699
721
  writer.${types_1.toReaderCall(field)}(${toNumber}(v));
700
722
  }
701
723
  writer.ldelim();
702
- `);
724
+ `;
725
+ if (isOptional) {
726
+ chunks.push(ts_poet_1.code `
727
+ if (message.${fieldName} !== undefined && message.${fieldName}.length !== 0) {
728
+ ${listWriteSnippet}
729
+ }
730
+ `);
731
+ }
732
+ else {
733
+ chunks.push(listWriteSnippet);
734
+ }
703
735
  }
704
736
  else {
705
737
  // Ideally we'd reuse `writeSnippet` but it has tagging embedded inside of it.
706
738
  const tag = ((field.number << 3) | 2) >>> 0;
707
- chunks.push(ts_poet_1.code `
739
+ const listWriteSnippet = ts_poet_1.code `
708
740
  writer.uint32(${tag}).fork();
709
741
  for (const v of message.${fieldName}) {
710
742
  writer.${types_1.toReaderCall(field)}(v);
711
743
  }
712
744
  writer.ldelim();
713
- `);
745
+ `;
746
+ if (isOptional) {
747
+ chunks.push(ts_poet_1.code `
748
+ if (message.${fieldName} !== undefined && message.${fieldName}.length !== 0) {
749
+ ${listWriteSnippet}
750
+ }
751
+ `);
752
+ }
753
+ else {
754
+ chunks.push(listWriteSnippet);
755
+ }
714
756
  }
715
757
  }
716
758
  else if (types_1.isWithinOneOfThatShouldBeUnion(options, field)) {
@@ -738,7 +780,7 @@ function generateEncode(ctx, fullName, messageDesc) {
738
780
  }
739
781
  else if (types_1.isScalar(field) || types_1.isEnum(field)) {
740
782
  chunks.push(ts_poet_1.code `
741
- if (${types_1.notDefaultCheck(ctx, field, `message.${fieldName}`)}) {
783
+ if (${types_1.notDefaultCheck(ctx, field, messageDesc.options, `message.${fieldName}`)}) {
742
784
  ${writeSnippet(`message.${fieldName}`)};
743
785
  }
744
786
  `);
@@ -763,7 +805,7 @@ function generateFromJson(ctx, fullName, messageDesc) {
763
805
  // create the basic function declaration
764
806
  chunks.push(ts_poet_1.code `
765
807
  fromJSON(${messageDesc.field.length > 0 ? 'object' : '_'}: any): ${fullName} {
766
- const message = { ...base${fullName} } as ${fullName};
808
+ const message = createBase${fullName}();
767
809
  `);
768
810
  // add a check for each incoming field
769
811
  messageDesc.field.forEach((field) => {
@@ -1064,7 +1106,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1064
1106
  const paramName = messageDesc.field.length > 0 ? 'object' : '_';
1065
1107
  chunks.push(ts_poet_1.code `
1066
1108
  fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
1067
- const message = { ...base${fullName} } as ${fullName};
1109
+ const message = createBase${fullName}();
1068
1110
  `);
1069
1111
  // add a check for each incoming field
1070
1112
  messageDesc.field.forEach((field) => {
package/build/options.js CHANGED
@@ -36,7 +36,7 @@ function defaultOptions() {
36
36
  context: false,
37
37
  snakeToCamel: ['json', 'keys'],
38
38
  forceLong: LongOption.NUMBER,
39
- useOptionals: false,
39
+ useOptionals: 'none',
40
40
  useDate: DateOption.DATE,
41
41
  oneof: OneofOption.PROPERTIES,
42
42
  esModuleInterop: false,
@@ -48,6 +48,7 @@ function defaultOptions() {
48
48
  outputTypeRegistry: false,
49
49
  stringEnums: false,
50
50
  constEnums: false,
51
+ enumsAsLiterals: false,
51
52
  outputClientImpl: true,
52
53
  outputServices: ServiceOption.DEFAULT,
53
54
  returnObservable: false,
package/build/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectBatchMethod = exports.responsePromiseOrObservable = exports.responseObservable = exports.responsePromise = exports.responseType = exports.requestType = exports.rawRequestType = exports.detectMapType = exports.toTypeName = exports.getEnumMethod = exports.messageToTypeName = exports.wrapperTypeName = exports.valueTypeName = exports.isEmptyType = exports.isLongValueType = exports.isStructTypeName = exports.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isBytesValueType = exports.isAnyValueTypeName = exports.isAnyValueType = exports.isValueType = exports.isTimestamp = exports.isMapType = exports.isWholeNumber = exports.isLong = exports.isRepeated = exports.isWithinOneOfThatShouldBeUnion = exports.isWithinOneOf = exports.isEnum = exports.isMessage = exports.isBytes = exports.isPrimitive = exports.isScalar = exports.createTypeMap = exports.notDefaultCheck = exports.defaultValue = exports.packedType = exports.toReaderCall = exports.basicTypeName = exports.basicLongWireType = exports.basicWireType = void 0;
3
+ exports.detectBatchMethod = exports.responsePromiseOrObservable = exports.responseObservable = exports.responsePromise = exports.responseType = exports.requestType = exports.rawRequestType = exports.detectMapType = exports.toTypeName = exports.getEnumMethod = exports.messageToTypeName = exports.wrapperTypeName = exports.valueTypeName = exports.isEmptyType = exports.isLongValueType = exports.isStructTypeName = exports.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isBytesValueType = exports.isAnyValueTypeName = exports.isAnyValueType = exports.isValueType = exports.isTimestamp = exports.isMapType = exports.isWholeNumber = exports.isLong = exports.isRepeated = exports.isWithinOneOfThatShouldBeUnion = exports.isWithinOneOf = exports.isEnum = exports.isMessage = exports.isBytes = exports.isPrimitive = exports.isOptionalProperty = exports.isScalar = exports.createTypeMap = exports.notDefaultCheck = exports.defaultValue = exports.packedType = exports.toReaderCall = exports.basicTypeName = exports.basicLongWireType = exports.basicWireType = void 0;
4
4
  const ts_proto_descriptors_1 = require("ts-proto-descriptors");
5
5
  const ts_poet_1 = require("ts-poet");
6
6
  const options_1 = require("./options");
@@ -225,8 +225,10 @@ function defaultValue(ctx, field) {
225
225
  }
226
226
  exports.defaultValue = defaultValue;
227
227
  /** Creates code that checks that the field is not the default value. Supports scalars and enums. */
228
- function notDefaultCheck(ctx, field, place) {
228
+ function notDefaultCheck(ctx, field, messageOptions, place) {
229
229
  const { typeMap, options } = ctx;
230
+ const isOptional = isOptionalProperty(field, messageOptions, options);
231
+ const maybeNotUndefinedAnd = isOptional ? `${place} !== undefined && ` : '';
230
232
  switch (field.type) {
231
233
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_DOUBLE:
232
234
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_FLOAT:
@@ -235,7 +237,7 @@ function notDefaultCheck(ctx, field, place) {
235
237
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_SINT32:
236
238
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_FIXED32:
237
239
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_SFIXED32:
238
- return ts_poet_1.code `${place} !== 0`;
240
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== 0`;
239
241
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_ENUM:
240
242
  // proto3 enforces enums starting at 0, however proto2 does not, so we have
241
243
  // to probe and see if zero is an allowed value. If it's not, pick the first one.
@@ -245,10 +247,10 @@ function notDefaultCheck(ctx, field, place) {
245
247
  const zerothValue = enumProto.value.find((v) => v.number === 0) || enumProto.value[0];
246
248
  if (options.stringEnums) {
247
249
  const enumType = messageToTypeName(ctx, field.typeName);
248
- return ts_poet_1.code `${place} !== ${enumType}.${zerothValue.name}`;
250
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== ${enumType}.${zerothValue.name}`;
249
251
  }
250
252
  else {
251
- return ts_poet_1.code `${place} !== ${zerothValue.number}`;
253
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== ${zerothValue.number}`;
252
254
  }
253
255
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_UINT64:
254
256
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_FIXED64:
@@ -256,20 +258,20 @@ function notDefaultCheck(ctx, field, place) {
256
258
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_SINT64:
257
259
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_SFIXED64:
258
260
  if (options.forceLong === options_1.LongOption.LONG) {
259
- return ts_poet_1.code `!${place}.isZero()`;
261
+ return ts_poet_1.code `${maybeNotUndefinedAnd} !${place}.isZero()`;
260
262
  }
261
263
  else if (options.forceLong === options_1.LongOption.STRING) {
262
- return ts_poet_1.code `${place} !== "0"`;
264
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== "0"`;
263
265
  }
264
266
  else {
265
- return ts_poet_1.code `${place} !== 0`;
267
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== 0`;
266
268
  }
267
269
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_BOOL:
268
270
  return ts_poet_1.code `${place} === true`;
269
271
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_STRING:
270
- return ts_poet_1.code `${place} !== ""`;
272
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place} !== ""`;
271
273
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_BYTES:
272
- return ts_poet_1.code `${place}.length !== 0`;
274
+ return ts_poet_1.code `${maybeNotUndefinedAnd} ${place}.length !== 0`;
273
275
  default:
274
276
  throw new Error('Not implemented for the given type.');
275
277
  }
@@ -314,6 +316,18 @@ function isScalar(field) {
314
316
  return scalarTypes.includes(field.type);
315
317
  }
316
318
  exports.isScalar = isScalar;
319
+ // When useOptionals='messages', non-scalar fields are translated into optional
320
+ // properties. When useOptionals='all', all fields are translated into
321
+ // optional properties, with the exception of map Entry key/values, which must
322
+ // always be present.
323
+ function isOptionalProperty(field, messageOptions, options) {
324
+ const optionalMessages = options.useOptionals === true || options.useOptionals === 'messages' || options.useOptionals === 'all';
325
+ const optionalAll = options.useOptionals === 'all';
326
+ return ((optionalMessages && isMessage(field) && !isRepeated(field)) ||
327
+ (optionalAll && !(messageOptions === null || messageOptions === void 0 ? void 0 : messageOptions.mapEntry)) ||
328
+ field.proto3Optional);
329
+ }
330
+ exports.isOptionalProperty = isOptionalProperty;
317
331
  /** This includes all scalars, enums and the [groups type](https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/DescriptorProtos.FieldDescriptorProto.Type.html#TYPE_GROUP) */
318
332
  function isPrimitive(field) {
319
333
  return !isMessage(field);
@@ -475,11 +489,14 @@ function messageToTypeName(ctx, protoType, typeOptions = {}) {
475
489
  // them to basic built-in types, we union the type with undefined to
476
490
  // indicate the value is optional. Exceptions:
477
491
  // - If the field is repeated, values cannot be undefined.
478
- // - If useOptionals=true, all non-scalar types are already optional
479
- // properties, so there's no need for that union.
492
+ // - If useOptionals='messages' or useOptionals='all', all non-scalar types
493
+ // are already optional properties, so there's no need for that union.
480
494
  let valueType = valueTypeName(ctx, protoType);
481
495
  if (!typeOptions.keepValueType && valueType) {
482
- if (!!typeOptions.repeated || options.useOptionals) {
496
+ if (!!typeOptions.repeated ||
497
+ options.useOptionals === true ||
498
+ options.useOptionals === 'messages' ||
499
+ options.useOptionals === 'all') {
483
500
  return valueType;
484
501
  }
485
502
  return ts_poet_1.code `${valueType} | undefined`;
@@ -522,18 +539,21 @@ function toTypeName(ctx, messageDesc, field) {
522
539
  // in messageToTypeName, so no need to consider them for that here.
523
540
  return type;
524
541
  }
525
- // By default (useOptionals=false, oneof=properties), non-scalar fields
542
+ // By default (useOptionals='none', oneof=properties), non-scalar fields
526
543
  // outside oneofs and all fields within a oneof clause need to be unioned
527
544
  // with `undefined` to indicate the value is optional.
528
545
  //
529
- // When useOptionals=true, non-scalar fields are translated to optional
530
- // properties, so no need for the union with `undefined` here.
546
+ // When useOptionals='messages' or useOptionals='all', non-scalar fields are
547
+ // translated to optional properties, so no need for the union with
548
+ // `undefined` here.
531
549
  //
532
550
  // When oneof=unions, we generate a single property for the entire `oneof`
533
551
  // clause, spelling each option out inside a large type union. No need for
534
552
  // union with `undefined` here, either.
535
553
  const { options } = ctx;
536
- if ((!isWithinOneOf(field) && isMessage(field) && !options.useOptionals) ||
554
+ if ((!isWithinOneOf(field) &&
555
+ isMessage(field) &&
556
+ (options.useOptionals === false || options.useOptionals === 'none')) ||
537
557
  (isWithinOneOf(field) && options.oneof === options_1.OneofOption.PROPERTIES) ||
538
558
  (isWithinOneOf(field) && field.proto3Optional)) {
539
559
  return ts_poet_1.code `${type} | undefined`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.94.0",
3
+ "version": "1.96.1",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",