ts-proto 1.96.0 → 1.97.2

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.97.2](https://github.com/stephenh/ts-proto/compare/v1.97.1...v1.97.2) (2022-01-06)
2
+
3
+
4
+ ### Performance Improvements
5
+
6
+ * fromJSON returns object literal to allow v8 optimizations ([#463](https://github.com/stephenh/ts-proto/issues/463)) ([5fcd05b](https://github.com/stephenh/ts-proto/commit/5fcd05b79e7c02547c4b6db46fae7a7202f97629))
7
+
8
+ ## [1.97.1](https://github.com/stephenh/ts-proto/compare/v1.97.0...v1.97.1) (2022-01-05)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * oneof=union breaks wrapper types [#458](https://github.com/stephenh/ts-proto/issues/458) ([#462](https://github.com/stephenh/ts-proto/issues/462)) ([dd16992](https://github.com/stephenh/ts-proto/commit/dd16992a409f24e88e3a142830cd0745f50dbd10))
14
+
15
+ # [1.97.0](https://github.com/stephenh/ts-proto/compare/v1.96.1...v1.97.0) (2021-12-30)
16
+
17
+
18
+ ### Features
19
+
20
+ * add an option to disable Exact types ([#456](https://github.com/stephenh/ts-proto/issues/456)) ([9c53d7e](https://github.com/stephenh/ts-proto/commit/9c53d7efb0252c7ea0af85a5d161ff94bcd69760))
21
+
22
+ ## [1.96.1](https://github.com/stephenh/ts-proto/compare/v1.96.0...v1.96.1) (2021-12-28)
23
+
24
+
25
+ ### Performance Improvements
26
+
27
+ * 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))
28
+
1
29
  # [1.96.0](https://github.com/stephenh/ts-proto/compare/v1.95.1...v1.96.0) (2021-12-24)
2
30
 
3
31
 
package/README.markdown CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  > `ts-proto` transforms your `.proto` files into strongly-typed, idiomatic TypeScript files!
7
7
 
8
- (Note, if you're a new user of ts-proto and using a modern TS setup with `esModuleInterop`, you need to also pass that as a `ts_proto_opt`.)
8
+ (Note, if you're a new user of ts-proto and using a modern TS setup with `esModuleInterop`, or want to use ts-proto in ESM / snowpack / vite, you need to also pass that as a `ts_proto_opt`.)
9
9
 
10
10
  ## Table of contents
11
11
 
@@ -350,6 +350,10 @@ Generated code will be placed in the Gradle build directory.
350
350
 
351
351
  - With `--ts_proto_opt=enumsAsLiterals=true`, the generated enum types will be enum-ish object with `as const`.
352
352
 
353
+ - With `--ts_proto_opt=useExactTypes=false`, the generated `fromPartial` method will not use Exact types.
354
+
355
+ The default behavior is `useExactTypes=true`, which makes `fromPartial` use Exact type for its argument to make TypeScript reject any unknown properties.
356
+
353
357
  ### Only Types
354
358
 
355
359
  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/main.js CHANGED
@@ -70,7 +70,7 @@ function generateFile(ctx, fileDesc) {
70
70
  // then add the encoder/decoder/base instance
71
71
  visit_1.visit(fileDesc, sourceInfo, (fullName, message, sInfo, fullProtoTypeName) => {
72
72
  const fullTypeName = utils_1.maybePrefixPackage(fileDesc, fullProtoTypeName);
73
- chunks.push(generateBaseInstance(ctx, fullName, message, fullTypeName));
73
+ chunks.push(generateBaseInstanceFactory(ctx, fullName, message, fullTypeName));
74
74
  const staticMembers = [];
75
75
  if (options.outputTypeRegistry) {
76
76
  staticMembers.push(ts_poet_1.code `$type: '${fullTypeName}' as const`);
@@ -86,8 +86,8 @@ function generateFile(ctx, fileDesc) {
86
86
  if (options.outputPartialMethods) {
87
87
  staticMembers.push(generateFromPartial(ctx, fullName, message));
88
88
  }
89
- staticMembers.push(...generateWrap(fullTypeName));
90
- staticMembers.push(...generateUnwrap(fullTypeName));
89
+ staticMembers.push(...generateWrap(ctx, fullTypeName));
90
+ staticMembers.push(...generateUnwrap(ctx, fullTypeName));
91
91
  chunks.push(ts_poet_1.code `
92
92
  export const ${ts_poet_1.def(fullName)} = {
93
93
  ${ts_poet_1.joinCode(staticMembers, { on: ',\n\n' })}
@@ -195,6 +195,7 @@ function makeUtils(options) {
195
195
  ...makeDeepPartial(options, longs),
196
196
  ...makeTimestampMethods(options, longs),
197
197
  ...longs,
198
+ ...makeComparisonUtils(),
198
199
  };
199
200
  }
200
201
  exports.makeUtils = makeUtils;
@@ -396,6 +397,17 @@ function makeTimestampMethods(options, longs) {
396
397
  `);
397
398
  return { toTimestamp, fromTimestamp, fromJsonTimestamp };
398
399
  }
400
+ function makeComparisonUtils() {
401
+ const isObject = ts_poet_1.conditionalOutput('isObject', ts_poet_1.code `
402
+ function isObject(value: any): boolean {
403
+ return typeof value === 'object' && value !== null;
404
+ }`);
405
+ const isSet = ts_poet_1.conditionalOutput('isSet', ts_poet_1.code `
406
+ function isSet(value: any): boolean {
407
+ return value !== null && value !== undefined;
408
+ }`);
409
+ return { isObject, isSet };
410
+ }
399
411
  // Create the interface with properties
400
412
  function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fullTypeName) {
401
413
  var _a;
@@ -460,20 +472,39 @@ function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
460
472
  return prop;
461
473
  */
462
474
  }
463
- // Create a 'base' instance with default values for decode to use as a prototype
464
- function generateBaseInstance(ctx, fullName, messageDesc, fullTypeName) {
465
- const fields = messageDesc.field
466
- .filter((field) => !types_1.isWithinOneOf(field))
467
- .map((field) => [field, types_1.defaultValue(ctx, field)])
468
- .filter(([field, val]) => val !== 'undefined' && !types_1.isBytes(field))
469
- .map(([field, val]) => {
475
+ // Create a function that constructs 'base' instance with default values for decode to use as a prototype
476
+ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
477
+ const fields = [];
478
+ // When oneof=unions, we generate a single property with an ADT per `oneof` clause.
479
+ const processedOneofs = new Set();
480
+ for (const field of messageDesc.field) {
481
+ if (types_1.isWithinOneOfThatShouldBeUnion(ctx.options, field)) {
482
+ const { oneofIndex } = field;
483
+ if (!processedOneofs.has(oneofIndex)) {
484
+ processedOneofs.add(oneofIndex);
485
+ const name = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[oneofIndex].name, ctx.options);
486
+ fields.push(ts_poet_1.code `${name}: undefined`);
487
+ }
488
+ continue;
489
+ }
470
490
  const name = case_1.maybeSnakeToCamel(field.name, ctx.options);
471
- return ts_poet_1.code `${name}: ${val}`;
472
- });
491
+ const val = types_1.isWithinOneOf(field)
492
+ ? 'undefined'
493
+ : types_1.isMapType(ctx, messageDesc, field)
494
+ ? '{}'
495
+ : types_1.isRepeated(field)
496
+ ? '[]'
497
+ : types_1.defaultValue(ctx, field);
498
+ fields.push(ts_poet_1.code `${name}: ${val}`);
499
+ }
473
500
  if (ctx.options.outputTypeRegistry) {
474
501
  fields.unshift(ts_poet_1.code `$type: '${fullTypeName}'`);
475
502
  }
476
- return ts_poet_1.code `const base${fullName}: object = { ${ts_poet_1.joinCode(fields, { on: ',' })} };`;
503
+ return ts_poet_1.code `
504
+ function createBase${fullName}(): ${fullName} {
505
+ return { ${ts_poet_1.joinCode(fields, { on: ',' })} };
506
+ }
507
+ `;
477
508
  }
478
509
  /** Creates a function to decode a message by loop overing the tags. */
479
510
  function generateDecode(ctx, fullName, messageDesc) {
@@ -487,22 +518,8 @@ function generateDecode(ctx, fullName, messageDesc) {
487
518
  ): ${fullName} {
488
519
  const reader = input instanceof ${Reader} ? input : new ${Reader}(input);
489
520
  let end = length === undefined ? reader.len : reader.pos + length;
490
- const message = { ...base${fullName} } as ${fullName};
521
+ const message = createBase${fullName}();
491
522
  `);
492
- // initialize all lists
493
- messageDesc.field.filter(types_1.isRepeated).forEach((field) => {
494
- const name = case_1.maybeSnakeToCamel(field.name, options);
495
- const value = types_1.isMapType(ctx, messageDesc, field) ? '{}' : '[]';
496
- chunks.push(ts_poet_1.code `message.${name} = ${value};`);
497
- });
498
- // initialize all buffers
499
- messageDesc.field
500
- .filter((field) => !types_1.isRepeated(field) && !types_1.isWithinOneOf(field) && types_1.isBytes(field))
501
- .forEach((field) => {
502
- const value = options.env === options_1.EnvOption.NODE ? 'Buffer.alloc(0)' : 'new Uint8Array()';
503
- const name = case_1.maybeSnakeToCamel(field.name, options);
504
- chunks.push(ts_poet_1.code `message.${name} = ${value};`);
505
- });
506
523
  // start the tag loop
507
524
  chunks.push(ts_poet_1.code `
508
525
  while (reader.pos < end) {
@@ -567,28 +584,29 @@ function generateDecode(ctx, fullName, messageDesc) {
567
584
  }
568
585
  // and then use the snippet to handle repeated fields if necessary
569
586
  if (types_1.isRepeated(field)) {
587
+ const maybeNonNullAssertion = ctx.options.useOptionals === 'all' ? '!' : '';
570
588
  if (types_1.isMapType(ctx, messageDesc, field)) {
571
589
  // We need a unique const within the `cast` statement
572
590
  const varName = `entry${field.number}`;
573
591
  chunks.push(ts_poet_1.code `
574
592
  const ${varName} = ${readSnippet};
575
593
  if (${varName}.value !== undefined) {
576
- message.${fieldName}[${varName}.key] = ${varName}.value;
594
+ message.${fieldName}${maybeNonNullAssertion}[${varName}.key] = ${varName}.value;
577
595
  }
578
596
  `);
579
597
  }
580
598
  else if (types_1.packedType(field.type) === undefined) {
581
- chunks.push(ts_poet_1.code `message.${fieldName}.push(${readSnippet});`);
599
+ chunks.push(ts_poet_1.code `message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});`);
582
600
  }
583
601
  else {
584
602
  chunks.push(ts_poet_1.code `
585
603
  if ((tag & 7) === 2) {
586
604
  const end2 = reader.uint32() + reader.pos;
587
605
  while (reader.pos < end2) {
588
- message.${fieldName}.push(${readSnippet});
606
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
589
607
  }
590
608
  } else {
591
- message.${fieldName}.push(${readSnippet});
609
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
592
610
  }
593
611
  `);
594
612
  }
@@ -796,16 +814,21 @@ function generateEncode(ctx, fullName, messageDesc) {
796
814
  function generateFromJson(ctx, fullName, messageDesc) {
797
815
  const { options, utils, typeMap } = ctx;
798
816
  const chunks = [];
817
+ const choiceFieldChunks = [];
799
818
  // create the basic function declaration
800
819
  chunks.push(ts_poet_1.code `
801
820
  fromJSON(${messageDesc.field.length > 0 ? 'object' : '_'}: any): ${fullName} {
802
- const message = { ...base${fullName} } as ${fullName};
821
+ return {
803
822
  `);
823
+ if (ctx.options.outputTypeRegistry) {
824
+ chunks.push(ts_poet_1.code `$type: ${fullName}.$type,`);
825
+ }
826
+ const oneofFieldsCases = messageDesc.oneofDecl.map((oneof, oneofIndex) => messageDesc.field.filter(types_1.isWithinOneOf).filter((field) => field.oneofIndex === oneofIndex));
804
827
  // add a check for each incoming field
805
828
  messageDesc.field.forEach((field) => {
806
829
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
807
830
  const jsonName = utils_1.determineFieldJsonName(field, options);
808
- // get a generic 'reader.doSomething' bit that is specific to the basic type
831
+ // get code that extracts value from incoming object
809
832
  const readSnippet = (from) => {
810
833
  if (types_1.isEnum(field)) {
811
834
  const fromJson = types_1.getEnumMethod(ctx, field.typeName, 'FromJSON');
@@ -840,6 +863,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
840
863
  else if (types_1.isAnyValueType(field) || types_1.isStructType(field)) {
841
864
  return ts_poet_1.code `${from}`;
842
865
  }
866
+ else if (types_1.isListValueType(field)) {
867
+ return ts_poet_1.code `[...${from}]`;
868
+ }
843
869
  else if (types_1.isValueType(ctx, field)) {
844
870
  const valueType = types_1.valueTypeName(ctx, field.typeName);
845
871
  if (types_1.isLongValueType(field) && options.forceLong === options_1.LongOption.LONG) {
@@ -848,9 +874,6 @@ function generateFromJson(ctx, fullName, messageDesc) {
848
874
  else if (types_1.isBytesValueType(field)) {
849
875
  return ts_poet_1.code `new ${case_1.capitalize(valueType.toCodeString())}(${from})`;
850
876
  }
851
- else if (types_1.isListValueType(field)) {
852
- return ts_poet_1.code `[...${from}]`;
853
- }
854
877
  else {
855
878
  return ts_poet_1.code `${case_1.capitalize(valueType.toCodeString())}(${from})`;
856
879
  }
@@ -913,56 +936,71 @@ function generateFromJson(ctx, fullName, messageDesc) {
913
936
  const fieldType = types_1.toTypeName(ctx, messageDesc, field);
914
937
  const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
915
938
  chunks.push(ts_poet_1.code `
916
- message.${fieldName} = Object.entries(object.${jsonName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
917
- acc[${i}] = ${readSnippet('value')};
918
- return acc;
919
- }, {});
920
- `);
921
- }
922
- else if (types_1.isAnyValueType(field)) {
923
- chunks.push(ts_poet_1.code `
924
- message.${fieldName} = Array.isArray(object?.${jsonName}) ? [...object.${jsonName}] : [];
939
+ ${fieldName}: ${ctx.utils.isObject}(object.${jsonName})
940
+ ? Object.entries(object.${jsonName}).reduce<${fieldType}>((acc, [key, value]) => {
941
+ acc[${i}] = ${readSnippet('value')};
942
+ return acc;
943
+ }, {})
944
+ : {},
925
945
  `);
926
946
  }
927
947
  else {
928
- // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
929
- chunks.push(ts_poet_1.code `
930
- message.${fieldName} = (object.${jsonName} ?? []).map((e: any) => ${readSnippet('e')});
931
- `);
948
+ const readValueSnippet = readSnippet('e');
949
+ if (readValueSnippet.toString() === ts_poet_1.code `e`.toString()) {
950
+ chunks.push(ts_poet_1.code `${fieldName}: Array.isArray(object?.${jsonName}) ? [...object.${jsonName}] : [],`);
951
+ }
952
+ else {
953
+ // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
954
+ chunks.push(ts_poet_1.code `
955
+ ${fieldName}: Array.isArray(object?.${jsonName}) ? object.${jsonName}.map((e: any) => ${readValueSnippet}): [],
956
+ `);
957
+ }
932
958
  }
933
959
  }
934
960
  else if (types_1.isWithinOneOfThatShouldBeUnion(options, field)) {
935
- chunks.push(ts_poet_1.code `if (object.${jsonName} !== undefined && object.${jsonName} !== null) {`);
936
- const oneofName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
937
- chunks.push(ts_poet_1.code `
938
- message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${jsonName}`)} }
939
- `);
940
- chunks.push(ts_poet_1.code `}`);
961
+ const cases = oneofFieldsCases[field.oneofIndex];
962
+ const firstCase = cases[0];
963
+ const lastCase = cases[cases.length - 1];
964
+ if (field === firstCase) {
965
+ const fieldName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
966
+ chunks.push(ts_poet_1.code `${fieldName}: `);
967
+ }
968
+ const ternaryIf = ts_poet_1.code `${ctx.utils.isSet}(object.${jsonName})`;
969
+ const ternaryThen = ts_poet_1.code `{ $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${jsonName}`)}`;
970
+ chunks.push(ts_poet_1.code `${ternaryIf} ? ${ternaryThen}} : `);
971
+ if (field === lastCase) {
972
+ chunks.push(ts_poet_1.code `undefined,`);
973
+ }
941
974
  }
942
975
  else if (types_1.isAnyValueType(field)) {
943
- chunks.push(ts_poet_1.code `message.${fieldName} = object.${fieldName};`);
976
+ chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isSet}(object?.${jsonName})
977
+ ? ${readSnippet(`object.${jsonName}`)}
978
+ : undefined,
979
+ `);
944
980
  }
945
981
  else if (types_1.isStructType(field)) {
946
- chunks.push(ts_poet_1.code `message.${fieldName} = typeof(object.${fieldName}) === 'object' ? object.${fieldName} : undefined;`);
982
+ chunks.push(ts_poet_1.code `${fieldName}: ${ctx.utils.isObject}(object.${jsonName})
983
+ ? ${readSnippet(`object.${jsonName}`)}
984
+ : undefined,`);
947
985
  }
948
986
  else if (types_1.isListValueType(field)) {
949
987
  chunks.push(ts_poet_1.code `
950
- message.${fieldName} = Array.isArray(object?.${fieldName})
951
- ? ${readSnippet(`object.${fieldName}`)}
952
- : ${'undefined'};
988
+ ${fieldName}: Array.isArray(object.${jsonName})
989
+ ? ${readSnippet(`object.${jsonName}`)}
990
+ : undefined,
953
991
  `);
954
992
  }
955
993
  else {
956
994
  const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
957
995
  chunks.push(ts_poet_1.code `
958
- message.${fieldName} = (object.${jsonName} !== undefined && object.${jsonName} !== null)
996
+ ${fieldName}: ${ctx.utils.isSet}(object.${jsonName})
959
997
  ? ${readSnippet(`object.${jsonName}`)}
960
- : ${fallback};
998
+ : ${fallback},
961
999
  `);
962
1000
  }
963
1001
  });
964
1002
  // and then wrap up the switch/while/return
965
- chunks.push(ts_poet_1.code `return message`);
1003
+ chunks.push(ts_poet_1.code `};`);
966
1004
  chunks.push(ts_poet_1.code `}`);
967
1005
  return ts_poet_1.joinCode(chunks, { on: '\n' });
968
1006
  }
@@ -1095,13 +1133,19 @@ function generateToJson(ctx, fullName, messageDesc) {
1095
1133
  function generateFromPartial(ctx, fullName, messageDesc) {
1096
1134
  const { options, utils, typeMap } = ctx;
1097
1135
  const chunks = [];
1098
- const Timestamp = utils_1.impProto(options, 'google/protobuf/timestamp', 'Timestamp');
1099
1136
  // create the basic function declaration
1100
1137
  const paramName = messageDesc.field.length > 0 ? 'object' : '_';
1101
- chunks.push(ts_poet_1.code `
1102
- fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
1103
- const message = { ...base${fullName} } as ${fullName};
1104
- `);
1138
+ if (ctx.options.useExactTypes) {
1139
+ chunks.push(ts_poet_1.code `
1140
+ fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
1141
+ `);
1142
+ }
1143
+ else {
1144
+ chunks.push(ts_poet_1.code `
1145
+ fromPartial(${paramName}: ${utils.DeepPartial}<${fullName}>): ${fullName} {
1146
+ `);
1147
+ }
1148
+ chunks.push(ts_poet_1.code `const message = createBase${fullName}();`);
1105
1149
  // add a check for each incoming field
1106
1150
  messageDesc.field.forEach((field) => {
1107
1151
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
@@ -1211,7 +1255,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1211
1255
  chunks.push(ts_poet_1.code `}`);
1212
1256
  return ts_poet_1.joinCode(chunks, { on: '\n' });
1213
1257
  }
1214
- function generateWrap(fullProtoTypeName) {
1258
+ function generateWrap(ctx, fullProtoTypeName) {
1215
1259
  const chunks = [];
1216
1260
  if (types_1.isStructTypeName(fullProtoTypeName)) {
1217
1261
  chunks.push(ts_poet_1.code `wrap(object: {[key: string]: any} | undefined): Struct {
@@ -1225,7 +1269,29 @@ function generateWrap(fullProtoTypeName) {
1225
1269
  }`);
1226
1270
  }
1227
1271
  if (types_1.isAnyValueTypeName(fullProtoTypeName)) {
1228
- chunks.push(ts_poet_1.code `wrap(value: any): Value {
1272
+ if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
1273
+ chunks.push(ts_poet_1.code `wrap(value: any): Value {
1274
+ if (value === null) {
1275
+ return {kind: {$case: 'nullValue', nullValue: NullValue.NULL_VALUE}};
1276
+ } else if (typeof value === 'boolean') {
1277
+ return {kind: {$case: 'boolValue', boolValue: value}};
1278
+ } else if (typeof value === 'number') {
1279
+ return {kind: {$case: 'numberValue', numberValue: value}};
1280
+ } else if (typeof value === 'string') {
1281
+ return {kind: {$case: 'stringValue', stringValue: value}};
1282
+ } else if (Array.isArray(value)) {
1283
+ return {kind: {$case: 'listValue', listValue: value}};
1284
+ } else if (typeof value === 'object') {
1285
+ return {kind: {$case: 'structValue', structValue: value}};
1286
+ } else if (typeof value === 'undefined') {
1287
+ return {} as Value;
1288
+ } else {
1289
+ throw new Error('Unsupported any value type: ' + typeof value);
1290
+ }
1291
+ }`);
1292
+ }
1293
+ else {
1294
+ chunks.push(ts_poet_1.code `wrap(value: any): Value {
1229
1295
  if (value === null) {
1230
1296
  return {nullValue: NullValue.NULL_VALUE} as Value;
1231
1297
  } else if (typeof value === 'boolean') {
@@ -1244,15 +1310,16 @@ function generateWrap(fullProtoTypeName) {
1244
1310
  throw new Error('Unsupported any value type: ' + typeof value);
1245
1311
  }
1246
1312
  }`);
1313
+ }
1247
1314
  }
1248
1315
  if (types_1.isListValueTypeName(fullProtoTypeName)) {
1249
- chunks.push(ts_poet_1.code `wrap(value: Array<any>): ListValue {
1250
- return {values: value};
1316
+ chunks.push(ts_poet_1.code `wrap(value: Array<any> | undefined): ListValue {
1317
+ return {values: value ?? []};
1251
1318
  }`);
1252
1319
  }
1253
1320
  return chunks;
1254
1321
  }
1255
- function generateUnwrap(fullProtoTypeName) {
1322
+ function generateUnwrap(ctx, fullProtoTypeName) {
1256
1323
  const chunks = [];
1257
1324
  if (types_1.isStructTypeName(fullProtoTypeName)) {
1258
1325
  chunks.push(ts_poet_1.code `unwrap(message: Struct): {[key: string]: any} {
@@ -1264,7 +1331,27 @@ function generateUnwrap(fullProtoTypeName) {
1264
1331
  }`);
1265
1332
  }
1266
1333
  if (types_1.isAnyValueTypeName(fullProtoTypeName)) {
1267
- chunks.push(ts_poet_1.code `unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
1334
+ if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
1335
+ chunks.push(ts_poet_1.code `unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
1336
+ if (message.kind?.$case === 'nullValue') {
1337
+ return null;
1338
+ } else if (message.kind?.$case === 'numberValue') {
1339
+ return message.kind?.numberValue;
1340
+ } else if (message.kind?.$case === 'stringValue') {
1341
+ return message.kind?.stringValue;
1342
+ } else if (message.kind?.$case === 'boolValue') {
1343
+ return message.kind?.boolValue;
1344
+ } else if (message.kind?.$case === 'structValue') {
1345
+ return message.kind?.structValue;
1346
+ } else if (message.kind?.$case === 'listValue') {
1347
+ return message.kind?.listValue;
1348
+ } else {
1349
+ return undefined;
1350
+ }
1351
+ }`);
1352
+ }
1353
+ else {
1354
+ chunks.push(ts_poet_1.code `unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
1268
1355
  if (message?.stringValue !== undefined) {
1269
1356
  return message.stringValue;
1270
1357
  } else if (message?.numberValue !== undefined) {
@@ -1280,6 +1367,7 @@ function generateUnwrap(fullProtoTypeName) {
1280
1367
  }
1281
1368
  return undefined;
1282
1369
  }`);
1370
+ }
1283
1371
  }
1284
1372
  if (types_1.isListValueTypeName(fullProtoTypeName)) {
1285
1373
  chunks.push(ts_poet_1.code `unwrap(message: ListValue): Array<any> {
package/build/options.js CHANGED
@@ -61,6 +61,7 @@ function defaultOptions() {
61
61
  outputSchema: false,
62
62
  onlyTypes: false,
63
63
  emitImportedFiles: true,
64
+ useExactTypes: true,
64
65
  };
65
66
  }
66
67
  exports.defaultOptions = defaultOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.96.0",
3
+ "version": "1.97.2",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",