ts-proto 1.170.0 → 1.171.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.markdown CHANGED
@@ -547,6 +547,58 @@ Generated code will be placed in the Gradle build directory.
547
547
 
548
548
  - With `--ts_proto_opt=comments=false`, comments won't be copied from the proto files to the generated code.
549
549
 
550
+ - With `--ts_proto_opt=useNullAsOptional=true`, `undefined` values will be converted to `null`, and if you use `optional` label in your `.proto` file, the field will have `undefined` type as well. for example:
551
+
552
+ ```protobuf
553
+ message ProfileInfo {
554
+ int32 id = 1;
555
+ string bio = 2;
556
+ string phone = 3;
557
+ }
558
+
559
+ message Department {
560
+ int32 id = 1;
561
+ string name = 2;
562
+ }
563
+
564
+ message User {
565
+ int32 id = 1;
566
+ string username = 2;
567
+ /*
568
+ ProfileInfo will be optional in typescript, the type will be ProfileInfo | null | undefined
569
+ this is needed in cases where you don't wanna provide any value for the profile.
570
+ */
571
+ optional ProfileInfo profile = 3;
572
+
573
+ /*
574
+ Department only accepts a Department type or null, so this means you have to pass it null if there is no value available.
575
+ */
576
+ Department department = 4;
577
+ }
578
+ ```
579
+
580
+ the generated interfaces will be:
581
+
582
+ ```typescript
583
+ export interface ProfileInfo {
584
+ id: number;
585
+ bio: string;
586
+ phone: string;
587
+ }
588
+
589
+ export interface Department {
590
+ id: number;
591
+ name: string;
592
+ }
593
+
594
+ export interface User {
595
+ id: number;
596
+ username: string;
597
+ profile?: ProfileInfo | null | undefined; // check this one
598
+ department: Department | null; // check this one
599
+ }
600
+ ```
601
+
550
602
  ### NestJS Support
551
603
 
552
604
  We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown).
@@ -724,6 +776,28 @@ As this will automatically enforce only one of `field_a` or `field_b` "being set
724
776
 
725
777
  In ts-proto's currently-unscheduled 2.x release, `oneof=unions` will become the default behavior.
726
778
 
779
+ ## OneOf Type Helpers
780
+
781
+ The following helper types may make it easier to work with the types generated from `oneof=unions`:
782
+
783
+ ```ts
784
+ /** Extracts all the case names from a oneOf field. */
785
+ type OneOfCases<T> = T extends { $case: infer U extends string } ? U : never;
786
+
787
+ /** Extracts a union of all the value types from a oneOf field */
788
+ type OneOfValues<T> = T extends { $case: infer U extends string; [key: string]: unknown }
789
+ ? T[U]
790
+ : never;
791
+
792
+ /** Extracts the specific type of a oneOf case based on its field name */
793
+ type OneOfCase<T, K extends OneOfCases<T>> = T extends {
794
+ $case: infer U extends K;
795
+ [key: string]: unknown;
796
+ }
797
+ ? T[U]
798
+ : never;
799
+ ```
800
+
727
801
  # Default values and unset fields
728
802
 
729
803
  In core Protobuf (and so also `ts-proto`), values that are _unset_ or equal to the default value are not sent over the wire.
package/build/main.js CHANGED
@@ -715,7 +715,7 @@ function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
715
715
  return (0, ts_poet_1.code) `{ ${mbReadonly}$case: '${fieldName}', ${mbReadonly}${fieldName}: ${typeName} }`;
716
716
  }), { on: " | " });
717
717
  const name = (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, options);
718
- return (0, ts_poet_1.code) `${mbReadonly}${name}?: ${unionType} | undefined,`;
718
+ return (0, ts_poet_1.code) `${mbReadonly}${name}?: ${unionType} | ${(0, utils_1.nullOrUndefined)(options)},`;
719
719
  /*
720
720
  // Ideally we'd put the comments for each oneof field next to the anonymous
721
721
  // type we've created in the type union above, but ts-poet currently lacks
@@ -751,7 +751,7 @@ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
751
751
  const name = options.useJsonName
752
752
  ? (0, utils_1.getFieldName)(field, options)
753
753
  : (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, ctx.options);
754
- fields.push((0, ts_poet_1.code) `${(0, utils_1.safeAccessor)(name)}: undefined`);
754
+ fields.push((0, ts_poet_1.code) `${(0, utils_1.safeAccessor)(name)}: ${(0, utils_1.nullOrUndefined)(options)}`);
755
755
  }
756
756
  continue;
757
757
  }
@@ -761,7 +761,7 @@ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
761
761
  }
762
762
  const fieldKey = (0, utils_1.safeAccessor)((0, utils_1.getFieldName)(field, options));
763
763
  const val = (0, types_1.isWithinOneOf)(field)
764
- ? "undefined"
764
+ ? (0, utils_1.nullOrUndefined)(options)
765
765
  : (0, types_1.isMapType)(ctx, messageDesc, field)
766
766
  ? (0, types_1.shouldGenerateJSMapType)(ctx, messageDesc, field)
767
767
  ? "new Map()"
@@ -910,14 +910,14 @@ function generateDecode(ctx, fullName, messageDesc) {
910
910
  }
911
911
  const initializerSnippet = initializerNecessary
912
912
  ? `
913
- if (${messageProperty} === undefined) {
913
+ if (${messageProperty} === undefined ${(0, utils_1.withOrMaybeCheckIsNull)(options, messageProperty)}) {
914
914
  ${messageProperty} = ${generateMapType ? "new Map()" : "{}"};
915
915
  }`
916
916
  : "";
917
917
  chunks.push((0, ts_poet_1.code) `
918
918
  ${tagCheck}
919
919
  const ${varName} = ${readSnippet};
920
- if (${varName}.value !== undefined) {
920
+ if (${varName}.value !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, `${varName}.value`)}) {
921
921
  ${initializerSnippet}
922
922
  ${valueSetterSnippet};
923
923
  }
@@ -926,7 +926,7 @@ function generateDecode(ctx, fullName, messageDesc) {
926
926
  else {
927
927
  const initializerSnippet = initializerNecessary
928
928
  ? `
929
- if (${messageProperty} === undefined) {
929
+ if (${messageProperty} === undefined ${(0, utils_1.withOrMaybeCheckIsNull)(options, messageProperty)}) {
930
930
  ${messageProperty} = [];
931
931
  }`
932
932
  : "";
@@ -992,7 +992,7 @@ function generateDecode(ctx, fullName, messageDesc) {
992
992
  let maybeNonNullAssertion = options.initializeFieldsAsUndefined ? "!" : "";
993
993
  if (!options.initializeFieldsAsUndefined) {
994
994
  unknownFieldsInitializerSnippet = `
995
- if (message._unknownFields === undefined) {
995
+ if (message._unknownFields === undefined ${(0, utils_1.withOrMaybeCheckIsNull)(options, `message._unknownFields`)}) {
996
996
  message._unknownFields = {};
997
997
  }
998
998
  `;
@@ -1005,7 +1005,7 @@ function generateDecode(ctx, fullName, messageDesc) {
1005
1005
  ${unknownFieldsInitializerSnippet}
1006
1006
  const list = message._unknownFields${maybeNonNullAssertion}[tag];
1007
1007
 
1008
- if (list === undefined) {
1008
+ if (list === undefined ${(0, utils_1.withOrMaybeCheckIsNull)(options, `message._unknownFields`)}) {
1009
1009
  message._unknownFields${maybeNonNullAssertion}[tag] = [buf];
1010
1010
  } else {
1011
1011
  list.push(buf);
@@ -1123,7 +1123,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1123
1123
  const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1124
1124
  const entryWriteSnippet = (0, types_1.isValueType)(ctx, valueType)
1125
1125
  ? (0, ts_poet_1.code) `
1126
- if (value !== undefined) {
1126
+ if (value !== undefined ${(0, utils_1.withOrMaybeCheckIsNotNull)(options, `value`)}) {
1127
1127
  ${writeSnippet(`{ ${maybeTypeField} key: key as any, value }`)};
1128
1128
  }
1129
1129
  `
@@ -1234,7 +1234,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1234
1234
  }
1235
1235
  if (isOptional) {
1236
1236
  chunks.push((0, ts_poet_1.code) `
1237
- if (${messageProperty} !== undefined && ${messageProperty}.length !== 0) {
1237
+ if (${messageProperty} !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, messageProperty)} && ${messageProperty}.length !== 0) {
1238
1238
  ${listWriteSnippet}
1239
1239
  }
1240
1240
  `);
@@ -1264,14 +1264,14 @@ function generateEncode(ctx, fullName, messageDesc) {
1264
1264
  else if ((0, types_1.isWithinOneOf)(field)) {
1265
1265
  // Oneofs don't have a default value check b/c they need to denote which-oneof presence
1266
1266
  chunks.push((0, ts_poet_1.code) `
1267
- if (${messageProperty} !== undefined) {
1267
+ if (${messageProperty} !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, messageProperty)}) {
1268
1268
  ${writeSnippet(`${messageProperty}`)};
1269
1269
  }
1270
1270
  `);
1271
1271
  }
1272
1272
  else if ((0, types_1.isMessage)(field)) {
1273
1273
  chunks.push((0, ts_poet_1.code) `
1274
- if (${messageProperty} !== undefined) {
1274
+ if (${messageProperty} !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, messageProperty)}) {
1275
1275
  ${writeSnippet(`${messageProperty}`)};
1276
1276
  }
1277
1277
  `);
@@ -1697,7 +1697,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1697
1697
  const fieldType = (0, types_1.toTypeName)(ctx, messageDesc, field);
1698
1698
  const i = convertFromObjectKey(ctx, messageDesc, field, "key");
1699
1699
  if ((0, types_1.shouldGenerateJSMapType)(ctx, messageDesc, field)) {
1700
- const fallback = noDefaultValue ? "undefined" : "new Map()";
1700
+ const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "new Map()";
1701
1701
  chunks.push((0, ts_poet_1.code) `
1702
1702
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1703
1703
  ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
@@ -1708,7 +1708,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1708
1708
  `);
1709
1709
  }
1710
1710
  else {
1711
- const fallback = noDefaultValue ? "undefined" : "{}";
1711
+ const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "{}";
1712
1712
  chunks.push((0, ts_poet_1.code) `
1713
1713
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1714
1714
  ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
@@ -1720,7 +1720,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1720
1720
  }
1721
1721
  }
1722
1722
  else {
1723
- const fallback = noDefaultValue ? "undefined" : "[]";
1723
+ const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "[]";
1724
1724
  const readValueSnippet = readSnippet("e");
1725
1725
  if (readValueSnippet.toString() === (0, ts_poet_1.code) `e`.toString()) {
1726
1726
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] : [],`);
@@ -1745,29 +1745,29 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1745
1745
  const ternaryThen = (0, ts_poet_1.code) `{ $case: '${fieldName}', ${fieldKey}: ${readSnippet(`${jsonProperty}`)}`;
1746
1746
  chunks.push((0, ts_poet_1.code) `${ternaryIf} ? ${ternaryThen}} : `);
1747
1747
  if (field === lastCase) {
1748
- chunks.push((0, ts_poet_1.code) `undefined,`);
1748
+ chunks.push((0, ts_poet_1.code) `${(0, utils_1.nullOrUndefined)(options)},`);
1749
1749
  }
1750
1750
  }
1751
1751
  else if ((0, types_1.isAnyValueType)(field)) {
1752
1752
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isSet}(${jsonPropertyOptional})
1753
1753
  ? ${readSnippet(`${jsonProperty}`)}
1754
- : undefined,
1754
+ : ${(0, utils_1.nullOrUndefined)(options)},
1755
1755
  `);
1756
1756
  }
1757
1757
  else if ((0, types_1.isStructType)(field)) {
1758
1758
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1759
1759
  ? ${readSnippet(`${jsonProperty}`)}
1760
- : undefined,`);
1760
+ : ${(0, utils_1.nullOrUndefined)(options)},`);
1761
1761
  }
1762
1762
  else if ((0, types_1.isListValueType)(field)) {
1763
1763
  chunks.push((0, ts_poet_1.code) `
1764
1764
  ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonProperty})
1765
1765
  ? ${readSnippet(`${jsonProperty}`)}
1766
- : undefined,
1766
+ : ${(0, utils_1.nullOrUndefined)(options)},
1767
1767
  `);
1768
1768
  }
1769
1769
  else {
1770
- const fallback = (0, types_1.isWithinOneOf)(field) || noDefaultValue ? "undefined" : (0, types_1.defaultValue)(ctx, field);
1770
+ const fallback = (0, types_1.isWithinOneOf)(field) || noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : (0, types_1.defaultValue)(ctx, field);
1771
1771
  chunks.push((0, ts_poet_1.code) `
1772
1772
  ${fieldKey}: ${ctx.utils.isSet}(${jsonProperty})
1773
1773
  ? ${readSnippet(`${jsonProperty}`)}
@@ -1780,9 +1780,9 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1780
1780
  chunks.push((0, ts_poet_1.code) `}`);
1781
1781
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
1782
1782
  }
1783
- function generateCanonicalToJson(fullName, fullProtobufTypeName, { useOptionals }) {
1783
+ function generateCanonicalToJson(fullName, fullProtobufTypeName, { useOptionals, useNullAsOptional }) {
1784
1784
  if ((0, types_1.isFieldMaskTypeName)(fullProtobufTypeName)) {
1785
- const returnType = useOptionals === "all" ? "string | undefined" : "string";
1785
+ const returnType = useOptionals === "all" ? `string | ${(0, utils_1.nullOrUndefined)({ useNullAsOptional })}` : "string";
1786
1786
  const pathModifier = useOptionals === "all" ? "?" : "";
1787
1787
  return (0, ts_poet_1.code) `
1788
1788
  toJSON(message: ${fullName}): ${returnType} {
@@ -1954,7 +1954,7 @@ function generateToJson(ctx, fullName, fullProtobufTypeName, messageDesc) {
1954
1954
  let emitDefaultValuesForJson = ctx.options.emitDefaultValues.includes("json-methods");
1955
1955
  const check = ((0, types_1.isScalar)(field) || (0, types_1.isEnum)(field)) && !((0, types_1.isWithinOneOf)(field) || emitDefaultValuesForJson)
1956
1956
  ? (0, types_1.notDefaultCheck)(ctx, field, messageDesc.options, `${messageProperty}`)
1957
- : `${messageProperty} !== undefined`;
1957
+ : `${messageProperty} !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, messageProperty)}`;
1958
1958
  chunks.push((0, ts_poet_1.code) `
1959
1959
  if (${check}) {
1960
1960
  ${jsonProperty} = ${readSnippet(`${messageProperty}`)};
package/build/options.js CHANGED
@@ -100,6 +100,7 @@ function defaultOptions() {
100
100
  comments: true,
101
101
  disableProto2Optionals: false,
102
102
  disableProto2DefaultValues: false,
103
+ useNullAsOptional: false,
103
104
  };
104
105
  }
105
106
  exports.defaultOptions = defaultOptions;
package/build/types.js CHANGED
@@ -235,7 +235,7 @@ function defaultValue(ctx, field) {
235
235
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_MESSAGE:
236
236
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_GROUP:
237
237
  default:
238
- return "undefined";
238
+ return (0, utils_1.nullOrUndefined)(options);
239
239
  }
240
240
  }
241
241
  exports.defaultValue = defaultValue;
@@ -243,7 +243,9 @@ exports.defaultValue = defaultValue;
243
243
  function notDefaultCheck(ctx, field, messageOptions, place) {
244
244
  const { typeMap, options, currentFile } = ctx;
245
245
  const isOptional = isOptionalProperty(field, messageOptions, options, currentFile.isProto3Syntax);
246
- const maybeNotUndefinedAnd = isOptional ? `${place} !== undefined && ` : "";
246
+ const maybeNotUndefinedAnd = isOptional
247
+ ? `${place} !== undefined ${(0, utils_1.withAndMaybeCheckIsNotNull)(options, place)} &&`
248
+ : "";
247
249
  switch (field.type) {
248
250
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_DOUBLE:
249
251
  case ts_proto_descriptors_1.FieldDescriptorProto_Type.TYPE_FLOAT:
@@ -552,7 +554,7 @@ function messageToTypeName(ctx, protoType, typeOptions = {}) {
552
554
  if ((_a = typeOptions.repeated) !== null && _a !== void 0 ? _a : false) {
553
555
  return valueType;
554
556
  }
555
- return (0, ts_poet_1.code) `${valueType} | undefined`;
557
+ return (0, ts_poet_1.code) `${valueType} | ${(0, utils_1.nullOrUndefined)(options)}`;
556
558
  }
557
559
  // Look for other special prototypes like Timestamp that aren't technically wrapper types
558
560
  if (!typeOptions.keepValueType && protoType === ".google.protobuf.Timestamp") {
@@ -584,7 +586,7 @@ exports.getEnumMethod = getEnumMethod;
584
586
  function toTypeName(ctx, messageDesc, field, ensureOptional = false) {
585
587
  function finalize(type, isOptional) {
586
588
  if (isOptional) {
587
- return (0, ts_poet_1.code) `${type} | undefined`;
589
+ return (0, ts_poet_1.code) `${type} | ${(0, utils_1.nullOrUndefined)(ctx.options, field.proto3Optional)}`;
588
590
  }
589
591
  return type;
590
592
  }
package/build/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.arrowFunction = exports.tryCatchBlock = exports.impProto = exports.impFile = exports.getPropertyAccessor = exports.safeAccessor = exports.getFieldName = exports.getFieldJsonName = exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.generateIndexFiles = exports.protoFilesToGenerate = void 0;
3
+ exports.withAndMaybeCheckIsNull = exports.withAndMaybeCheckIsNotNull = exports.withOrMaybeCheckIsNull = exports.withOrMaybeCheckIsNotNull = exports.maybeCheckIsNull = exports.maybeCheckIsNotNull = exports.nullOrUndefined = exports.arrowFunction = exports.tryCatchBlock = exports.impProto = exports.impFile = exports.getPropertyAccessor = exports.safeAccessor = exports.getFieldName = exports.getFieldJsonName = exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.generateIndexFiles = exports.protoFilesToGenerate = void 0;
4
4
  const path = require("path");
5
5
  const ts_poet_1 = require("ts-poet");
6
6
  const options_1 = require("./options");
@@ -261,3 +261,31 @@ function arrowFunction(params, body, isOneLine = true) {
261
261
  return (0, ts_poet_1.code) `(${params}) => { ${body} }`;
262
262
  }
263
263
  exports.arrowFunction = arrowFunction;
264
+ function nullOrUndefined(options, hasProto3Optional = false) {
265
+ return options.useNullAsOptional ? `null ${hasProto3Optional ? "| undefined" : ""}` : "undefined";
266
+ }
267
+ exports.nullOrUndefined = nullOrUndefined;
268
+ function maybeCheckIsNotNull(options, typeName, prefix) {
269
+ return options.useNullAsOptional ? ` ${prefix} ${typeName} !== null` : "";
270
+ }
271
+ exports.maybeCheckIsNotNull = maybeCheckIsNotNull;
272
+ function maybeCheckIsNull(options, typeName, prefix) {
273
+ return options.useNullAsOptional ? ` ${prefix} ${typeName} === null` : "";
274
+ }
275
+ exports.maybeCheckIsNull = maybeCheckIsNull;
276
+ function withOrMaybeCheckIsNotNull(options, typeName) {
277
+ return maybeCheckIsNotNull(options, typeName, "||");
278
+ }
279
+ exports.withOrMaybeCheckIsNotNull = withOrMaybeCheckIsNotNull;
280
+ function withOrMaybeCheckIsNull(options, typeName) {
281
+ return maybeCheckIsNull(options, typeName, "||");
282
+ }
283
+ exports.withOrMaybeCheckIsNull = withOrMaybeCheckIsNull;
284
+ function withAndMaybeCheckIsNotNull(options, typeName) {
285
+ return maybeCheckIsNotNull(options, typeName, "&&");
286
+ }
287
+ exports.withAndMaybeCheckIsNotNull = withAndMaybeCheckIsNotNull;
288
+ function withAndMaybeCheckIsNull(options, typeName) {
289
+ return maybeCheckIsNotNull(options, typeName, "&&");
290
+ }
291
+ exports.withAndMaybeCheckIsNull = withAndMaybeCheckIsNull;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.170.0",
3
+ "version": "1.171.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",