ts-proto 1.128.0 → 1.130.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.130.0](https://github.com/stephenh/ts-proto/compare/v1.129.0...v1.130.0) (2022-10-22)
2
+
3
+
4
+ ### Features
5
+
6
+ * support `useReadonlyTypes` option ([#691](https://github.com/stephenh/ts-proto/issues/691)) ([4b87334](https://github.com/stephenh/ts-proto/commit/4b8733452fb59cea3b9fb4721159b97e6df59854))
7
+
8
+ # [1.129.0](https://github.com/stephenh/ts-proto/compare/v1.128.0...v1.129.0) (2022-10-16)
9
+
10
+
11
+ ### Features
12
+
13
+ * support `useMapType` option ([#686](https://github.com/stephenh/ts-proto/issues/686)) ([f2e80ab](https://github.com/stephenh/ts-proto/commit/f2e80ab3ecc3a438ecbd88b2170b8119ebadfcd3))
14
+
1
15
  # [1.128.0](https://github.com/stephenh/ts-proto/compare/v1.127.0...v1.128.0) (2022-10-13)
2
16
 
3
17
 
package/README.markdown CHANGED
@@ -458,6 +458,12 @@ Generated code will be placed in the Gradle build directory.
458
458
  - **Note**: Uses are accummulated, so multiple values are expected in the form of `--ts_proto_opt=M... --ts_proto_opt=M...` (one `ts_proto_opt` per mapping).
459
459
  - **Note**: Proto files that match mapped imports **will not be generated**.
460
460
 
461
+ - With `--ts_proto_opt=useMapType=true`, the generated code for protobuf `map<key_type, value_type>` will become `Map<key_type, value_type>` that uses JavaScript Map type.
462
+
463
+ The default behavior is `useMapType=false`, which makes it generate the code for protobuf `map<key_type, value_type` with the key-value pair like `{[key: key_type]: value_type}`.
464
+
465
+ - With `--ts_proto_opt=useReadonlyTypes=true`, the generated types will be declared as immutable using typescript's `readonly` modifer.
466
+
461
467
  ### NestJS Support
462
468
 
463
469
  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).
package/build/main.js CHANGED
@@ -530,7 +530,7 @@ function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fu
530
530
  const name = (0, case_1.maybeSnakeToCamel)(fieldDesc.name, options);
531
531
  const type = (0, types_1.toTypeName)(ctx, messageDesc, fieldDesc);
532
532
  const q = (0, types_1.isOptionalProperty)(fieldDesc, messageDesc.options, options) ? "?" : "";
533
- chunks.push((0, ts_poet_1.code) `${name}${q}: ${type}, `);
533
+ chunks.push((0, ts_poet_1.code) `${ctx.options.useReadonlyTypes ? "readonly " : ""}${name}${q}: ${type}, `);
534
534
  });
535
535
  chunks.push((0, ts_poet_1.code) `}`);
536
536
  return (0, ts_poet_1.joinCode)(chunks, { on: "\n" });
@@ -589,7 +589,9 @@ function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) {
589
589
  const val = (0, types_1.isWithinOneOf)(field)
590
590
  ? "undefined"
591
591
  : (0, types_1.isMapType)(ctx, messageDesc, field)
592
- ? "{}"
592
+ ? ctx.options.useMapType
593
+ ? "new Map()"
594
+ : "{}"
593
595
  : (0, types_1.isRepeated)(field)
594
596
  ? "[]"
595
597
  : (0, types_1.defaultValue)(ctx, field);
@@ -621,8 +623,8 @@ function generateDecode(ctx, fullName, messageDesc) {
621
623
  ): ${fullName} {
622
624
  const reader = input instanceof ${Reader} ? input : new ${Reader}(input);
623
625
  let end = length === undefined ? reader.len : reader.pos + length;
624
- const message = ${createBase};
625
626
  `);
627
+ chunks.push((0, ts_poet_1.code) `const message = ${createBase}${options.useReadonlyTypes ? " as any" : ""};`);
626
628
  if (options.unknownFields) {
627
629
  chunks.push((0, ts_poet_1.code) `(message as any)._unknownFields = {}`);
628
630
  }
@@ -698,10 +700,13 @@ function generateDecode(ctx, fullName, messageDesc) {
698
700
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
699
701
  // We need a unique const within the `cast` statement
700
702
  const varName = `entry${field.number}`;
703
+ const valueSetterSnippet = ctx.options.useMapType
704
+ ? `message.${fieldName}${maybeNonNullAssertion}.set(${varName}.key, ${varName}.value)`
705
+ : `message.${fieldName}${maybeNonNullAssertion}[${varName}.key] = ${varName}.value`;
701
706
  chunks.push((0, ts_poet_1.code) `
702
707
  const ${varName} = ${readSnippet};
703
708
  if (${varName}.value !== undefined) {
704
- message.${fieldName}${maybeNonNullAssertion}[${varName}.key] = ${varName}.value;
709
+ ${valueSetterSnippet};
705
710
  }
706
711
  `);
707
712
  }
@@ -822,11 +827,20 @@ function generateEncode(ctx, fullName, messageDesc) {
822
827
  `
823
828
  : writeSnippet(`{ ${maybeTypeField} key: key as any, value }`);
824
829
  const optionalAlternative = isOptional ? " || {}" : "";
825
- chunks.push((0, ts_poet_1.code) `
826
- Object.entries(message.${fieldName}${optionalAlternative}).forEach(([key, value]) => {
827
- ${entryWriteSnippet}
828
- });
829
- `);
830
+ if (ctx.options.useMapType) {
831
+ chunks.push((0, ts_poet_1.code) `
832
+ message.${fieldName}${optionalAlternative}.forEach((value, key) => {
833
+ ${entryWriteSnippet}
834
+ });
835
+ `);
836
+ }
837
+ else {
838
+ chunks.push((0, ts_poet_1.code) `
839
+ Object.entries(message.${fieldName}${optionalAlternative}).forEach(([key, value]) => {
840
+ ${entryWriteSnippet}
841
+ });
842
+ `);
843
+ }
830
844
  }
831
845
  else if ((0, types_1.packedType)(field.type) === undefined) {
832
846
  const listWriteSnippet = (0, ts_poet_1.code) `
@@ -1099,14 +1113,26 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1099
1113
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1100
1114
  const fieldType = (0, types_1.toTypeName)(ctx, messageDesc, field);
1101
1115
  const i = maybeCastToNumber(ctx, messageDesc, field, "key");
1102
- chunks.push((0, ts_poet_1.code) `
1103
- ${fieldName}: ${ctx.utils.isObject}(${jsonProperty})
1104
- ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
1105
- acc[${i}] = ${readSnippet("value")};
1106
- return acc;
1107
- }, {})
1108
- : {},
1109
- `);
1116
+ if (ctx.options.useMapType) {
1117
+ chunks.push((0, ts_poet_1.code) `
1118
+ ${fieldName}: ${ctx.utils.isObject}(${jsonProperty})
1119
+ ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
1120
+ acc.set(${i}, ${readSnippet("value")});
1121
+ return acc;
1122
+ }, new Map())
1123
+ : new Map(),
1124
+ `);
1125
+ }
1126
+ else {
1127
+ chunks.push((0, ts_poet_1.code) `
1128
+ ${fieldName}: ${ctx.utils.isObject}(${jsonProperty})
1129
+ ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
1130
+ acc[${i}] = ${readSnippet("value")};
1131
+ return acc;
1132
+ }, {})
1133
+ : {},
1134
+ `);
1135
+ }
1110
1136
  }
1111
1137
  else {
1112
1138
  const readValueSnippet = readSnippet("e");
@@ -1286,14 +1312,26 @@ function generateToJson(ctx, fullName, fullProtobufTypeName, messageDesc) {
1286
1312
  };
1287
1313
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1288
1314
  // Maps might need their values transformed, i.e. bytes --> base64
1289
- chunks.push((0, ts_poet_1.code) `
1290
- ${jsonProperty} = {};
1291
- if (message.${fieldName}) {
1292
- Object.entries(message.${fieldName}).forEach(([k, v]) => {
1293
- ${jsonProperty}[k] = ${readSnippet("v")};
1294
- });
1295
- }
1296
- `);
1315
+ if (ctx.options.useMapType) {
1316
+ chunks.push((0, ts_poet_1.code) `
1317
+ ${jsonProperty} = {};
1318
+ if (message.${fieldName}) {
1319
+ message.${fieldName}.forEach((v, k) => {
1320
+ ${jsonProperty}[k] = ${readSnippet("v")};
1321
+ });
1322
+ }
1323
+ `);
1324
+ }
1325
+ else {
1326
+ chunks.push((0, ts_poet_1.code) `
1327
+ ${jsonProperty} = {};
1328
+ if (message.${fieldName}) {
1329
+ Object.entries(message.${fieldName}).forEach(([k, v]) => {
1330
+ ${jsonProperty}[k] = ${readSnippet("v")};
1331
+ });
1332
+ }
1333
+ `);
1334
+ }
1297
1335
  }
1298
1336
  else if ((0, types_1.isRepeated)(field)) {
1299
1337
  // Arrays might need their elements transformed
@@ -1339,7 +1377,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1339
1377
  if (options.usePrototypeForDefaults) {
1340
1378
  createBase = (0, ts_poet_1.code) `Object.create(${createBase}) as ${fullName}`;
1341
1379
  }
1342
- chunks.push((0, ts_poet_1.code) `const message = ${createBase};`);
1380
+ chunks.push((0, ts_poet_1.code) `const message = ${createBase}${options.useReadonlyTypes ? " as any" : ""};`);
1343
1381
  // add a check for each incoming field
1344
1382
  messageDesc.field.forEach((field) => {
1345
1383
  const fieldName = (0, case_1.maybeSnakeToCamel)(field.name, options);
@@ -1408,14 +1446,29 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1408
1446
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1409
1447
  const fieldType = (0, types_1.toTypeName)(ctx, messageDesc, field);
1410
1448
  const i = maybeCastToNumber(ctx, messageDesc, field, "key");
1411
- chunks.push((0, ts_poet_1.code) `
1412
- message.${fieldName} = Object.entries(object.${fieldName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
1413
- if (value !== undefined) {
1414
- acc[${i}] = ${readSnippet("value")};
1415
- }
1416
- return acc;
1417
- }, {});
1418
- `);
1449
+ if (ctx.options.useMapType) {
1450
+ chunks.push((0, ts_poet_1.code) `
1451
+ message.${fieldName} = (() => {
1452
+ const m = new Map();
1453
+ (object.${fieldName} as ${fieldType} ?? new Map()).forEach((value, key) => {
1454
+ if (value !== undefined) {
1455
+ m.set(${i}, ${readSnippet("value")});
1456
+ }
1457
+ });
1458
+ return m;
1459
+ })();
1460
+ `);
1461
+ }
1462
+ else {
1463
+ chunks.push((0, ts_poet_1.code) `
1464
+ message.${fieldName} = Object.entries(object.${fieldName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
1465
+ if (value !== undefined) {
1466
+ acc[${i}] = ${readSnippet("value")};
1467
+ }
1468
+ return acc;
1469
+ }, {});
1470
+ `);
1471
+ }
1419
1472
  }
1420
1473
  else {
1421
1474
  chunks.push((0, ts_poet_1.code) `
@@ -1471,7 +1524,7 @@ function generateWrap(ctx, fullProtoTypeName, fieldNames) {
1471
1524
  if ((0, types_1.isAnyValueTypeName)(fullProtoTypeName)) {
1472
1525
  if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
1473
1526
  chunks.push((0, ts_poet_1.code) `wrap(value: any): Value {
1474
- const result = createBaseValue();
1527
+ const result = createBaseValue()${ctx.options.useReadonlyTypes ? " as any" : ""};
1475
1528
 
1476
1529
  if (value === null) {
1477
1530
  result.kind = {$case: '${fieldNames.nullValue}', ${fieldNames.nullValue}: NullValue.NULL_VALUE};
@@ -1494,7 +1547,7 @@ function generateWrap(ctx, fullProtoTypeName, fieldNames) {
1494
1547
  }
1495
1548
  else {
1496
1549
  chunks.push((0, ts_poet_1.code) `wrap(value: any): Value {
1497
- const result = createBaseValue();
1550
+ const result = createBaseValue()${ctx.options.useReadonlyTypes ? " as any" : ""};
1498
1551
 
1499
1552
  if (value === null) {
1500
1553
  result.${fieldNames.nullValue} = NullValue.NULL_VALUE;
@@ -1517,8 +1570,8 @@ function generateWrap(ctx, fullProtoTypeName, fieldNames) {
1517
1570
  }
1518
1571
  }
1519
1572
  if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
1520
- chunks.push((0, ts_poet_1.code) `wrap(value: Array<any> | undefined): ListValue {
1521
- const result = createBaseListValue();
1573
+ chunks.push((0, ts_poet_1.code) `wrap(value: ${ctx.options.useReadonlyTypes ? "ReadonlyArray<any>" : "Array<any>"} | undefined): ListValue {
1574
+ const result = createBaseListValue()${ctx.options.useReadonlyTypes ? " as any" : ""};
1522
1575
 
1523
1576
  result.values = value ?? [];
1524
1577
 
@@ -1526,8 +1579,8 @@ function generateWrap(ctx, fullProtoTypeName, fieldNames) {
1526
1579
  }`);
1527
1580
  }
1528
1581
  if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
1529
- chunks.push((0, ts_poet_1.code) `wrap(paths: string[]): FieldMask {
1530
- const result = createBaseFieldMask();
1582
+ chunks.push((0, ts_poet_1.code) `wrap(paths: ${ctx.options.useReadonlyTypes ? "readonly " : ""} string[]): FieldMask {
1583
+ const result = createBaseFieldMask()${ctx.options.useReadonlyTypes ? " as any" : ""};
1531
1584
 
1532
1585
  result.paths = paths;
1533
1586
 
@@ -1587,12 +1640,12 @@ function generateUnwrap(ctx, fullProtoTypeName, fieldNames) {
1587
1640
  }
1588
1641
  }
1589
1642
  if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
1590
- chunks.push((0, ts_poet_1.code) `unwrap(message: ListValue): Array<any> {
1643
+ chunks.push((0, ts_poet_1.code) `unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "ListValue"}): Array<any> {
1591
1644
  return message.values;
1592
1645
  }`);
1593
1646
  }
1594
1647
  if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
1595
- chunks.push((0, ts_poet_1.code) `unwrap(message: FieldMask): string[] {
1648
+ chunks.push((0, ts_poet_1.code) `unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "FieldMask"}): string[] {
1596
1649
  return message.paths;
1597
1650
  }`);
1598
1651
  }
package/build/options.js CHANGED
@@ -72,6 +72,8 @@ function defaultOptions() {
72
72
  useJsonWireFormat: false,
73
73
  useNumericEnumForJson: false,
74
74
  initializeFieldsAsUndefined: true,
75
+ useMapType: false,
76
+ useReadonlyTypes: false,
75
77
  M: {},
76
78
  };
77
79
  }
package/build/types.js CHANGED
@@ -457,13 +457,17 @@ function valueTypeName(ctx, typeName) {
457
457
  ? (0, ts_poet_1.code) `string`
458
458
  : (0, ts_poet_1.code) `Uint8Array`;
459
459
  case ".google.protobuf.ListValue":
460
- return (0, ts_poet_1.code) `Array<any>`;
460
+ return ctx.options.useReadonlyTypes ? (0, ts_poet_1.code) `ReadonlyArray<any>` : (0, ts_poet_1.code) `Array<any>`;
461
461
  case ".google.protobuf.Value":
462
462
  return (0, ts_poet_1.code) `any`;
463
463
  case ".google.protobuf.Struct":
464
- return (0, ts_poet_1.code) `{[key: string]: any}`;
464
+ return ctx.options.useReadonlyTypes ? (0, ts_poet_1.code) `{readonly [key: string]: any}` : (0, ts_poet_1.code) `{[key: string]: any}`;
465
465
  case ".google.protobuf.FieldMask":
466
- return ctx.options.useJsonWireFormat ? (0, ts_poet_1.code) `string` : (0, ts_poet_1.code) `string[]`;
466
+ return ctx.options.useJsonWireFormat
467
+ ? (0, ts_poet_1.code) `string`
468
+ : ctx.options.useReadonlyTypes
469
+ ? (0, ts_poet_1.code) `readonly string[]`
470
+ : (0, ts_poet_1.code) `string[]`;
467
471
  case ".google.protobuf.Duration":
468
472
  return ctx.options.useJsonWireFormat ? (0, ts_poet_1.code) `string` : undefined;
469
473
  case ".google.protobuf.Timestamp":
@@ -557,8 +561,14 @@ function toTypeName(ctx, messageDesc, field) {
557
561
  const mapType = detectMapType(ctx, messageDesc, field);
558
562
  if (mapType) {
559
563
  const { keyType, valueType } = mapType;
564
+ if (ctx.options.useMapType) {
565
+ return (0, ts_poet_1.code) `Map<${keyType}, ${valueType}>`;
566
+ }
560
567
  return (0, ts_poet_1.code) `{ [key: ${keyType} ]: ${valueType} }`;
561
568
  }
569
+ if (ctx.options.useReadonlyTypes) {
570
+ return (0, ts_poet_1.code) `readonly ${type}[]`;
571
+ }
562
572
  return (0, ts_poet_1.code) `${type}[]`;
563
573
  }
564
574
  if (isValueType(ctx, field)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.128.0",
3
+ "version": "1.130.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",