ts-proto 1.95.0 → 1.97.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,31 @@
1
+ # [1.97.0](https://github.com/stephenh/ts-proto/compare/v1.96.1...v1.97.0) (2021-12-30)
2
+
3
+
4
+ ### Features
5
+
6
+ * 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))
7
+
8
+ ## [1.96.1](https://github.com/stephenh/ts-proto/compare/v1.96.0...v1.96.1) (2021-12-28)
9
+
10
+
11
+ ### Performance Improvements
12
+
13
+ * 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))
14
+
15
+ # [1.96.0](https://github.com/stephenh/ts-proto/compare/v1.95.1...v1.96.0) (2021-12-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * `enumsAsLiterals` option ([#450](https://github.com/stephenh/ts-proto/issues/450)) ([fcaade2](https://github.com/stephenh/ts-proto/commit/fcaade2855ae28ea3553a365556ccb92a9644d70))
21
+
22
+ ## [1.95.1](https://github.com/stephenh/ts-proto/compare/v1.95.0...v1.95.1) (2021-12-23)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * Add service to the client constructor. ([#455](https://github.com/stephenh/ts-proto/issues/455)) ([8c32104](https://github.com/stephenh/ts-proto/commit/8c32104a8522cfe2febcf2338d51710021d837ff))
28
+
1
29
  # [1.95.0](https://github.com/stephenh/ts-proto/compare/v1.94.0...v1.95.0) (2021-12-14)
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
 
@@ -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,12 @@ 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
+
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
+
351
357
  ### Only Types
352
358
 
353
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/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
@@ -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`);
@@ -460,20 +460,39 @@ function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
460
460
  return prop;
461
461
  */
462
462
  }
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]) => {
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
+ }
470
478
  const name = case_1.maybeSnakeToCamel(field.name, ctx.options);
471
- return ts_poet_1.code `${name}: ${val}`;
472
- });
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
+ }
473
488
  if (ctx.options.outputTypeRegistry) {
474
489
  fields.unshift(ts_poet_1.code `$type: '${fullTypeName}'`);
475
490
  }
476
- 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
+ `;
477
496
  }
478
497
  /** Creates a function to decode a message by loop overing the tags. */
479
498
  function generateDecode(ctx, fullName, messageDesc) {
@@ -487,22 +506,8 @@ function generateDecode(ctx, fullName, messageDesc) {
487
506
  ): ${fullName} {
488
507
  const reader = input instanceof ${Reader} ? input : new ${Reader}(input);
489
508
  let end = length === undefined ? reader.len : reader.pos + length;
490
- const message = { ...base${fullName} } as ${fullName};
509
+ const message = createBase${fullName}();
491
510
  `);
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
511
  // start the tag loop
507
512
  chunks.push(ts_poet_1.code `
508
513
  while (reader.pos < end) {
@@ -567,28 +572,29 @@ function generateDecode(ctx, fullName, messageDesc) {
567
572
  }
568
573
  // and then use the snippet to handle repeated fields if necessary
569
574
  if (types_1.isRepeated(field)) {
575
+ const maybeNonNullAssertion = ctx.options.useOptionals === 'all' ? '!' : '';
570
576
  if (types_1.isMapType(ctx, messageDesc, field)) {
571
577
  // We need a unique const within the `cast` statement
572
578
  const varName = `entry${field.number}`;
573
579
  chunks.push(ts_poet_1.code `
574
580
  const ${varName} = ${readSnippet};
575
581
  if (${varName}.value !== undefined) {
576
- message.${fieldName}[${varName}.key] = ${varName}.value;
582
+ message.${fieldName}${maybeNonNullAssertion}[${varName}.key] = ${varName}.value;
577
583
  }
578
584
  `);
579
585
  }
580
586
  else if (types_1.packedType(field.type) === undefined) {
581
- chunks.push(ts_poet_1.code `message.${fieldName}.push(${readSnippet});`);
587
+ chunks.push(ts_poet_1.code `message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});`);
582
588
  }
583
589
  else {
584
590
  chunks.push(ts_poet_1.code `
585
591
  if ((tag & 7) === 2) {
586
592
  const end2 = reader.uint32() + reader.pos;
587
593
  while (reader.pos < end2) {
588
- message.${fieldName}.push(${readSnippet});
594
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
589
595
  }
590
596
  } else {
591
- message.${fieldName}.push(${readSnippet});
597
+ message.${fieldName}${maybeNonNullAssertion}.push(${readSnippet});
592
598
  }
593
599
  `);
594
600
  }
@@ -799,7 +805,7 @@ function generateFromJson(ctx, fullName, messageDesc) {
799
805
  // create the basic function declaration
800
806
  chunks.push(ts_poet_1.code `
801
807
  fromJSON(${messageDesc.field.length > 0 ? 'object' : '_'}: any): ${fullName} {
802
- const message = { ...base${fullName} } as ${fullName};
808
+ const message = createBase${fullName}();
803
809
  `);
804
810
  // add a check for each incoming field
805
811
  messageDesc.field.forEach((field) => {
@@ -1095,13 +1101,19 @@ function generateToJson(ctx, fullName, messageDesc) {
1095
1101
  function generateFromPartial(ctx, fullName, messageDesc) {
1096
1102
  const { options, utils, typeMap } = ctx;
1097
1103
  const chunks = [];
1098
- const Timestamp = utils_1.impProto(options, 'google/protobuf/timestamp', 'Timestamp');
1099
1104
  // create the basic function declaration
1100
1105
  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
- `);
1106
+ if (ctx.options.useExactTypes) {
1107
+ chunks.push(ts_poet_1.code `
1108
+ fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
1109
+ `);
1110
+ }
1111
+ else {
1112
+ chunks.push(ts_poet_1.code `
1113
+ fromPartial(${paramName}: ${utils.DeepPartial}<${fullName}>): ${fullName} {
1114
+ `);
1115
+ }
1116
+ chunks.push(ts_poet_1.code `const message = createBase${fullName}();`);
1105
1117
  // add a check for each incoming field
1106
1118
  messageDesc.field.forEach((field) => {
1107
1119
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
package/build/options.js CHANGED
@@ -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,
@@ -60,6 +61,7 @@ function defaultOptions() {
60
61
  outputSchema: false,
61
62
  onlyTypes: false,
62
63
  emitImportedFiles: true,
64
+ useExactTypes: true,
63
65
  };
64
66
  }
65
67
  exports.defaultOptions = defaultOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.95.0",
3
+ "version": "1.97.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",