ts-proto 1.86.0 → 1.89.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.89.0](https://github.com/stephenh/ts-proto/compare/v1.88.0...v1.89.0) (2021-11-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * Improve map reading (fromJSON/fromPartial) ([#410](https://github.com/stephenh/ts-proto/issues/410)) ([057d438](https://github.com/stephenh/ts-proto/commit/057d438548d95c354331f7d2d767ccff952ad5c6))
7
+
8
+ # [1.88.0](https://github.com/stephenh/ts-proto/compare/v1.87.1...v1.88.0) (2021-11-22)
9
+
10
+
11
+ ### Features
12
+
13
+ * Support for Google.Protobuf.Value, ListValue and Struct ([#396](https://github.com/stephenh/ts-proto/issues/396)) ([7dd9c16](https://github.com/stephenh/ts-proto/commit/7dd9c16ffdec4d9ea296fbdc30d390fe44192c42))
14
+
15
+ ## [1.87.1](https://github.com/stephenh/ts-proto/compare/v1.87.0...v1.87.1) (2021-11-21)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * code generation for int64 map values in fromPartial and fromJson ([#395](https://github.com/stephenh/ts-proto/issues/395)) ([d3ea8eb](https://github.com/stephenh/ts-proto/commit/d3ea8eb69e19a5e45fcc1766c4af1194b17e48fc))
21
+
22
+ # [1.87.0](https://github.com/stephenh/ts-proto/compare/v1.86.0...v1.87.0) (2021-11-16)
23
+
24
+
25
+ ### Features
26
+
27
+ * Use ternary operator for conditional assignments ([#394](https://github.com/stephenh/ts-proto/issues/394)) ([d84c084](https://github.com/stephenh/ts-proto/commit/d84c084fb56c958c184f8971479979b8bfb17ccc))
28
+
1
29
  # [1.86.0](https://github.com/stephenh/ts-proto/compare/v1.85.0...v1.86.0) (2021-11-15)
2
30
 
3
31
 
package/README.markdown CHANGED
@@ -22,10 +22,13 @@
22
22
  - [Assumptions](#assumptions)
23
23
  - [Todo](#todo)
24
24
  - [OneOf Handling](#oneof-handling)
25
- - [Primitive Types](#primitive-types)
25
+ - [Default Values and Unset Fields](#default-values-and-unset-fields)
26
+ - [Well-Known Types](#well-known-types)
27
+ - [Wrapper Types](#wrapper-types)
28
+ - [JSON Types (Struct Types)](#json-types-struct-types)
29
+ - [Timestamps](#timestamp)
26
30
  - [Wrapper Types](#wrapper-types)
27
31
  - [Number Types](#number-types)
28
- - [Timestamps](#timestamps)
29
32
  - [Current Status of Optional Values](#current-status-of-optional-values)
30
33
 
31
34
  # Overview
@@ -70,7 +73,7 @@ It will also generate client implementations of `PingService`; currently [Twirp]
70
73
  - `npm install ts-proto`
71
74
  - `protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. ./simple.proto`
72
75
  - (Note that the output parameter name, `ts_proto_out`, is named based on the suffix of the plugin's name, i.e. "ts_proto" suffix in the `--plugin=./node_modules/.bin/protoc-gen-ts_proto` parameter becomes the `_out` prefix, per `protoc`'s CLI conventions.)
73
- - On Windows, use `protoc --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd --ts_proto_out=. ./simple.proto`
76
+ - On Windows, use `protoc --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd --ts_proto_out=. ./simple.proto` (see [#93](https://github.com/stephenh/ts-proto/issues/93))
74
77
  - Ensure you're using a modern `protoc`, i.e. the original `protoc` `3.0.0` doesn't support the `_opt` flag
75
78
 
76
79
  This will generate `*.ts` source files for the given `*.proto` types.
@@ -399,29 +402,201 @@ As this will automatically enforce only one of `field_a` or `field_b` "being set
399
402
 
400
403
  In ts-proto's currently-unscheduled 2.x release, `oneof=unions` will become the default behavior.
401
404
 
402
- # Primitive Types
405
+ # Default values and unset fields
403
406
 
404
- Protobuf has the somewhat annoying behavior that primitives types cannot differentiate between set-to-defalut-value and unset.
407
+ In core Protobuf, values that are _unset_ or equal to the default value are not sent over the wire.
408
+ The default value of a message is `undefined`. Primitive types take their natural default value, i.e. `string` is `''`, `number` is `0`, etc.
409
+ This behavior enables forward compatibility, as primitive fields will always have a value, even when omitted by outdated agents, but it also means _default_ and _unset_ values cannot be distinguished.
405
410
 
406
- I.e. if you have a `string name = 1`, and set `object.name = ''`, Protobuf will skip sending the tagged `name` field over the wire, because its understood that readers on the other end will, when they see `name` is not included in the payload, return empty string.
411
+ If you need primitive fields where you can detect set/unset, see [Wrapper Types](#wrapper-types).
407
412
 
408
- `ts-proto` models this behavior, of "unset" values being the primitive's default. (Technically by setting up an object prototype that knows the default values of the message's primitive fields.)
413
+ **Encode / Decode**
409
414
 
410
- If you want fields where you can model set/unset, see Wrapper Types.
415
+ `ts-proto` follows the Protobuf rules, and always returns default values for unsets fields when decoding, while omitting them from the output when serialized in binary format.
411
416
 
412
- # Wrapper Types
417
+ ```protobuf
418
+ syntax = "proto3";
419
+ message Foo {
420
+ string bar = 1;
421
+ }
422
+ ```
423
+
424
+ ```typescript
425
+ protobufBytes; // assume this is an empty Foo object, in protobuf binary format
426
+ Foo.decode(protobufBytes); // => { bar: '' }
427
+ ```
428
+
429
+ ```typescript
430
+ Foo.encode({ bar: '' }); // => { }, writes an empty Foo object, in protobuf binary format
431
+ ```
432
+
433
+ **fromJSON / toJSON**
434
+
435
+ Reading JSON will also initialize the default values. Since senders may either omit unset fields, or set them to the default value, use `fromJSON` to normalize the input.
436
+
437
+ ```typescript
438
+ Foo.fromJSON({ }); // => { bar: '' }
439
+ Foo.fromJSON({ bar: '' }); // => { bar: '' }
440
+ Foo.fromJSON({ bar: 'baz' }); // => { bar: 'baz' }
441
+ ```
442
+
443
+ When writing JSON, `ts-proto` currently does **not** normalize message when converting to JSON, other than omitting unset fields, but it may do so in the future.
444
+
445
+ ```typescript
446
+ // Current ts-proto behavior
447
+ Foo.toJSON({ }); // => { }
448
+ Foo.toJSON({ bar: undefined }); // => { }
449
+ Foo.toJSON({ bar: '' }); // => { bar: '' } - note: this is the default value, but it's not omitted
450
+ Foo.toJSON({ bar: 'baz' }); // => { bar: 'baz' }
451
+ ```
452
+
453
+ ```typescript
454
+ // Possible future behavior, where ts-proto would normalize message
455
+ Foo.toJSON({ }); // => { }
456
+ Foo.toJSON({ bar: undefined }); // => { }
457
+ Foo.toJSON({ bar: '' }); // => { } - note: omitting the default value, as expected
458
+ Foo.toJSON({ bar: 'baz' }); // => { bar: 'baz' }
459
+ ```
413
460
 
414
- In core Protobuf, unset primitive fields become their respective default values (so you loose ability to distinguish "unset" from "default").
461
+ - Please open an issue if you need this behavior.
415
462
 
416
- However, unset message fields stay `null`.
463
+ # Well-Known Types
417
464
 
418
- This allows a cute hack where you can model a logical `string | unset` by creating a field that is technically a message (i.e. so it can stay `null` for the unset case), but the message only has a single string field (i.e for storing the value in the set case).
465
+ Protobuf comes with several predefined message definitions, called "[Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf)".
466
+ Their interpretation is defined by the Protobuf specification, and libraries are expected to convert these messages to corresponding native types in the target language.
419
467
 
420
- Protobuf has already "blessed" this pattern with several built-in types, i.e. `google.protobuf.StringValue`, `google.protobuf.Int32Value`, etc.
468
+ `ts-proto` currently automatically converts these messages to their corresponding native types.
421
469
 
422
- `ts-proto` understands these wrapper types and "re-idiomizes" them by generating a `google.protobuf.StringValue name = 1` field as a `name: string | undefined`, and hides the `StringValue` implementation detail from your code (i.e. during `encode`/`decode` of the `name` field on the wire to external consumers, it's still read/written as a `StringValue` message field).
470
+ - Wrapper Types:
471
+
472
+ * [google.protobuf.DoubleValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#DoubleValue) ⇆ `number | undefined`
473
+ * [google.protobuf.FloatValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#FloatValue) ⇆ `number | undefined`
474
+ * [google.protobuf.Int64Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Int64Value) ⇆ `number | undefined`
475
+ * [google.protobuf.UInt64Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#UInt64Value) ⇆ `number | undefined`
476
+ * [google.protobuf.Int32Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Int32Value) ⇆ `number | undefined`
477
+ * [google.protobuf.UInt32Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#UInt32Value) ⇆ `number | undefined`
478
+ * [google.protobuf.BoolValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#BoolValue) ⇆ `boolean | undefined`
479
+ * [google.protobuf.StringValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#StringValue) ⇆ `string | undefined`
480
+ * [google.protobuf.BytesValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.BytesValue) ⇆ `Uint8Array | undefined`
481
+
482
+ - JSON Types (Struct Types):
483
+
484
+ * [google.protobuf.Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Value) ⇆ `any | undefined` (i.e. `number | string | boolean | null | array | object`)
485
+ * [google.protobuf.ListValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#ListValue) ⇆ `any[]`
486
+ * [google.protobuf.Struct](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Struct) ⇆ `{ [key: string]: any } | undefined`
487
+
488
+ ## Wrapper Types
489
+
490
+ Wrapper Types are messages containing a single primitive field, and can be imported in `.proto` files with `import "google/protobuf/wrappers.proto"`.
491
+
492
+
493
+ Since these are _messages_, their default value is `undefined`, allowing you to distinguish unset primitives from their default values, when using Wrapper Types.
494
+ `ts-proto` generates these fields as `<primitive> | undefined`.
495
+
496
+ For example:
497
+
498
+ ```protobuf
499
+ // Protobuf
500
+ syntax = "proto3";
501
+
502
+ import "google/protobuf/wrappers.proto";
503
+
504
+ message ExampleMessage {
505
+ google.protobuf.StringValue name = 1;
506
+ }
507
+ ```
508
+
509
+ ```typescript
510
+ // TypeScript
511
+ interface ExampleMessage {
512
+ name: string | undefined;
513
+ }
514
+ ```
515
+
516
+ When encoding a message the primitive value is converted back to its corresponding wrapper type:
517
+
518
+ ```typescript
519
+ ExampleMessage.encode({ name: 'foo' }) // => { name: { value: 'foo' } }, in binary
520
+ ```
521
+
522
+ When calling toJSON, the value is not converted, because wrapper types are idiomatic in JSON.
523
+
524
+ ```typescript
525
+ ExampleMessage.toJSON({ name: 'foo' }) // => { name: 'foo' }
526
+ ```
527
+
528
+ ## JSON Types (Struct Types)
529
+
530
+ Protobuf's language and types are not sufficient to represent all possible JSON values, since JSON may contain values whose type is unknown in advance.
531
+ For this reason, Protobuf offers several additional types to represent arbitrary JSON values.
532
+
533
+ These are called Struct Types, and can be imported in `.proto` files with `import "google/protobuf/struct.proto"`.
534
+
535
+ - [google.protobuf.Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Value) &lrarr; `any`
536
+ - This is the most general type, and can represent any JSON value (i.e. `number | string | boolean | null | array | object`).
537
+ - [google.protobuf.ListValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#ListValue) &lrarr; `any[]`
538
+ - To represent a JSON array
539
+ - [google.protobuf.Struct](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Struct) &lrarr; `{ [key: string]: any }`
540
+ - To represent a JSON object
541
+
542
+ `ts-proto` automatically converts back and forth between these Struct Types and their corresponding JSON types.
543
+
544
+ Example:
545
+
546
+ ```protobuf
547
+ // Protobuf
548
+ syntax = "proto3";
549
+
550
+ import "google/protobuf/struct.proto";
551
+
552
+ message ExampleMessage {
553
+ google.protobuf.Value anything = 1;
554
+ }
555
+ ```
556
+
557
+ ```typescript
558
+ // TypeScript
559
+ interface ExampleMessage {
560
+ anything: any | undefined;
561
+ }
562
+ ```
563
+
564
+ Encoding a JSON value embedded in a message, converts it to a Struct Type:
565
+ ```typescript
566
+ ExampleMessage.encode({ anything: { "name": "hello" } })
567
+ /* Outputs the following structure, encoded in protobuf binary format:
568
+ {
569
+ anything: Value {
570
+ structValue = Struct {
571
+ fields = [
572
+ MapEntry {
573
+ key = "name",
574
+ value = Value {
575
+ stringValue = "hello"
576
+ }
577
+ ]
578
+ }
579
+ }
580
+ }
581
+ }*/
582
+
583
+ ExampleMessage.encode({ anything: true })
584
+ /* Outputs the following structure encoded in protobuf binary format:
585
+ {
586
+ anything: Value {
587
+ boolValue = true
588
+ }
589
+ }*/
590
+ ```
591
+
592
+ ## Timestamp
593
+
594
+ The representation of `google.protobuf.Timestamp` is configurable by the `useDate` flag.
595
+
596
+ | Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` |
597
+ | --------------------------- | ---------------------- | ------------------------------------ | ---------------- |
598
+ | `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` |
423
599
 
424
- This makes dealing with `string | unset` in your code much nicer, albeit it's unfortunate that, in Protobuf core, this is not as simple as marking a `string name = 1` field as `optional`, i.e. you have to "dirty" your proto files a bit by knowing to use the `StringValue` convention.
425
600
 
426
601
  # Number Types
427
602
 
@@ -452,14 +627,6 @@ The protobuf number types map to JavaScript types based on the `forceLong` confi
452
627
 
453
628
  Where (\*) indicates they might throw an error at runtime.
454
629
 
455
- # Timestamps
456
-
457
- The representation of `google.protobuf.Timestamp` is configurable by the `useDate` flag.
458
-
459
- | Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` |
460
- | --------------------------- | ---------------------- | ------------------------------------ | ---------------- |
461
- | `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` |
462
-
463
630
  # Current Status of Optional Values
464
631
 
465
632
  - Required primitives: use as-is, i.e. `string name = 1`.
package/build/encode.js CHANGED
@@ -32,6 +32,8 @@ function generateEncoder(ctx, typeName) {
32
32
  return ts_poet_1.code `${TypeValue}.encode({value: value ?? false}).finish()`;
33
33
  case 'BytesValue':
34
34
  return ts_poet_1.code `${TypeValue}.encode({value: value ?? new Uint8Array()}).finish()`;
35
+ case 'ListValue':
36
+ return ts_poet_1.code `${TypeValue}.encode({value: value ?? []]}).finish()`;
35
37
  }
36
38
  throw new Error(`unknown wrapper type: ${name}`);
37
39
  }
package/build/main.js CHANGED
@@ -184,6 +184,7 @@ function makeUtils(options) {
184
184
  ...makeDeepPartial(options, longs),
185
185
  ...makeTimestampMethods(options, longs),
186
186
  ...longs,
187
+ ...makeWrappingUtils(),
187
188
  };
188
189
  }
189
190
  exports.makeUtils = makeUtils;
@@ -240,6 +241,57 @@ function makeLongUtils(options, bytes) {
240
241
  `);
241
242
  return { numberToLong, longToNumber, longToString, longInit, Long };
242
243
  }
244
+ function makeWrappingUtils() {
245
+ const wrapAnyValue = ts_poet_1.conditionalOutput('wrapAnyValue', ts_poet_1.code `function wrapAnyValue(value: any): Value {
246
+ if (value === null) {
247
+ return {nullValue: 0} as Value;
248
+ } else if (typeof value === 'boolean') {
249
+ return {boolValue: value} as Value;
250
+ } else if (typeof value === 'number') {
251
+ return {numberValue: value} as Value;
252
+ } else if (typeof value === 'string') {
253
+ return {stringValue: value} as Value;
254
+ } else if (Array.isArray(value)) {
255
+ return {listValue: value} as Value;
256
+ } else if (typeof value === 'object') {
257
+ return {structValue: value} as Value;
258
+ } else if (typeof value === 'undefined') {
259
+ return {} as Value;
260
+ } else {
261
+ throw new Error('Unsupported any value type: ' + typeof value);
262
+ }
263
+ }`);
264
+ const unwrapAnyValue = ts_poet_1.conditionalOutput('unwrapAnyValue', ts_poet_1.code `function unwrapAnyValue(value: Value): string | number | boolean | Object | null | Array<any> | undefined {
265
+ if (value.stringValue !== undefined) {
266
+ return value.stringValue;
267
+ } else if (value.numberValue !== undefined) {
268
+ return value.numberValue;
269
+ } else if (value.boolValue !== undefined) {
270
+ return value.boolValue;
271
+ } else if (value.structValue !== undefined) {
272
+ return value.structValue;
273
+ } else if (value.listValue !== undefined) {
274
+ return value.listValue;
275
+ } else if (value.nullValue !== undefined) {
276
+ return null;
277
+ }
278
+ }`);
279
+ const wrapStruct = ts_poet_1.conditionalOutput('wrapStruct', ts_poet_1.code `function wrapStruct(object: {[key: string]: any}): Struct {
280
+ const struct = Struct.fromPartial({});
281
+ Object.keys(object).forEach(key => {
282
+ struct.fields[key] = object[key];
283
+ });
284
+ return struct;
285
+ }`);
286
+ const unwrapStruct = ts_poet_1.conditionalOutput('unwrapStruct', ts_poet_1.code `function unwrapStruct(struct: Struct): {[key: string]: any} {
287
+ const object: { [key: string]: any } = {};
288
+ Object.keys(struct.fields).forEach(key => {
289
+ object[key] = struct.fields[key];
290
+ });
291
+ return object;
292
+ }`);
293
+ return { wrapAnyValue, unwrapAnyValue, wrapStruct, unwrapStruct };
294
+ }
243
295
  function makeByteUtils() {
244
296
  const globalThis = ts_poet_1.conditionalOutput('globalThis', ts_poet_1.code `
245
297
  declare var self: any | undefined;
@@ -285,13 +337,15 @@ function makeDeepPartial(options, longs) {
285
337
  `;
286
338
  }
287
339
  const maybeExport = options.exportCommonSymbols ? 'export' : '';
288
- const maybeLong = options.forceLong === options_1.LongOption.LONG ? ts_poet_1.code ` | ${longs.Long}` : '';
340
+ // Allow passing longs as numbers or strings, nad we'll convert them
341
+ const maybeLong = options.forceLong === options_1.LongOption.LONG ? ts_poet_1.code ` : T extends ${longs.Long} ? string | number | Long ` : '';
289
342
  const keys = options.outputTypeRegistry ? ts_poet_1.code `Exclude<keyof T, '$type'>` : ts_poet_1.code `keyof T`;
290
343
  // Based on the type from ts-essentials
291
344
  const DeepPartial = ts_poet_1.conditionalOutput('DeepPartial', ts_poet_1.code `
292
- type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined${maybeLong};
293
- ${maybeExport} type DeepPartial<T> = T extends Builtin
345
+ type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
346
+ ${maybeExport} type DeepPartial<T> = T extends Builtin
294
347
  ? T
348
+ ${maybeLong}
295
349
  : T extends Array<infer U>
296
350
  ? Array<DeepPartial<U>>
297
351
  : T extends ReadonlyArray<infer U>
@@ -526,8 +580,18 @@ function generateDecode(ctx, fullName, messageDesc) {
526
580
  }
527
581
  }
528
582
  else if (types_1.isValueType(ctx, field)) {
583
+ const unwrap = (decodedValue) => {
584
+ if (types_1.isAnyValueType(field))
585
+ return ts_poet_1.code `${ctx.utils.unwrapAnyValue}(${decodedValue})`;
586
+ if (types_1.isStructType(field))
587
+ return ts_poet_1.code `${ctx.utils.unwrapStruct}(${decodedValue})`;
588
+ if (types_1.isListValueType(field))
589
+ return ts_poet_1.code `${decodedValue}.values`;
590
+ return ts_poet_1.code `${decodedValue}.value`;
591
+ };
529
592
  const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
530
- readSnippet = ts_poet_1.code `${type}.decode(reader, reader.uint32()).value`;
593
+ const decoder = ts_poet_1.code `${type}.decode(reader, reader.uint32())`;
594
+ readSnippet = ts_poet_1.code `${unwrap(decoder)}`;
531
595
  }
532
596
  else if (types_1.isTimestamp(field) && (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) {
533
597
  const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
@@ -622,10 +686,19 @@ function generateEncode(ctx, fullName, messageDesc) {
622
686
  writeSnippet = (place) => ts_poet_1.code `${type}.encode(${utils.toTimestamp}(${place}), writer.uint32(${tag}).fork()).ldelim()`;
623
687
  }
624
688
  else if (types_1.isValueType(ctx, field)) {
689
+ const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : '';
690
+ const wrappedValue = (place) => {
691
+ if (types_1.isAnyValueType(field))
692
+ return ts_poet_1.code `${ctx.utils.wrapAnyValue}(${place})`;
693
+ if (types_1.isListValueType(field))
694
+ return ts_poet_1.code `{values: ${place}}`;
695
+ if (types_1.isStructType(field))
696
+ return ts_poet_1.code `${ctx.utils.wrapStruct}(${place})`;
697
+ return ts_poet_1.code `{${maybeTypeField} value: ${place}!}`;
698
+ };
625
699
  const tag = ((field.number << 3) | 2) >>> 0;
626
700
  const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
627
- const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : '';
628
- writeSnippet = (place) => ts_poet_1.code `${type}.encode({ ${maybeTypeField} value: ${place}! }, writer.uint32(${tag}).fork()).ldelim()`;
701
+ writeSnippet = (place) => ts_poet_1.code `${type}.encode(${wrappedValue(place)}, writer.uint32(${tag}).fork()).ldelim()`;
629
702
  }
630
703
  else if (types_1.isMessage(field)) {
631
704
  const tag = ((field.number << 3) | 2) >>> 0;
@@ -773,6 +846,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
773
846
  (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.TIMESTAMP)) {
774
847
  return ts_poet_1.code `${utils.fromJsonTimestamp}(${from})`;
775
848
  }
849
+ else if (types_1.isAnyValueType(field) || types_1.isStructType(field)) {
850
+ return ts_poet_1.code `${from}`;
851
+ }
776
852
  else if (types_1.isValueType(ctx, field)) {
777
853
  const valueType = types_1.valueTypeName(ctx, field.typeName);
778
854
  if (types_1.isLongValueType(field) && options.forceLong === options_1.LongOption.LONG) {
@@ -781,6 +857,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
781
857
  else if (types_1.isBytesValueType(field)) {
782
858
  return ts_poet_1.code `new ${case_1.capitalize(valueType.toCodeString())}(${from})`;
783
859
  }
860
+ else if (types_1.isListValueType(field)) {
861
+ return ts_poet_1.code `[...${from}]`;
862
+ }
784
863
  else {
785
864
  return ts_poet_1.code `${case_1.capitalize(valueType.toCodeString())}(${from})`;
786
865
  }
@@ -798,6 +877,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
798
877
  return ts_poet_1.code `${utils.bytesFromBase64}(${from} as string)`;
799
878
  }
800
879
  }
880
+ else if (types_1.isLong(valueType) && options.forceLong === options_1.LongOption.LONG) {
881
+ return ts_poet_1.code `Long.fromString(${from} as string)`;
882
+ }
801
883
  else if (types_1.isEnum(valueType)) {
802
884
  return ts_poet_1.code `${from} as number`;
803
885
  }
@@ -817,6 +899,9 @@ function generateFromJson(ctx, fullName, messageDesc) {
817
899
  const type = types_1.basicTypeName(ctx, valueType);
818
900
  return ts_poet_1.code `${from} as ${type}`;
819
901
  }
902
+ else if (types_1.isAnyValueType(valueType)) {
903
+ return ts_poet_1.code `${from}`;
904
+ }
820
905
  else {
821
906
  const type = types_1.basicTypeName(ctx, valueType);
822
907
  return ts_poet_1.code `${type}.fromJSON(${from})`;
@@ -834,15 +919,19 @@ function generateFromJson(ctx, fullName, messageDesc) {
834
919
  // and then use the snippet to handle repeated fields if necessary
835
920
  if (types_1.isRepeated(field)) {
836
921
  if (types_1.isMapType(ctx, messageDesc, field)) {
837
- chunks.push(ts_poet_1.code `message.${fieldName} = {};`);
838
- chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
922
+ const fieldType = types_1.toTypeName(ctx, messageDesc, field);
839
923
  const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
840
924
  chunks.push(ts_poet_1.code `
841
- Object.entries(object.${fieldName}).forEach(([key, value]) => {
842
- message.${fieldName}[${i}] = ${readSnippet('value')};
843
- });
925
+ message.${fieldName} = Object.entries(object.${fieldName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
926
+ acc[${i}] = ${readSnippet('value')};
927
+ return acc;
928
+ }, {});
929
+ `);
930
+ }
931
+ else if (types_1.isAnyValueType(field)) {
932
+ chunks.push(ts_poet_1.code `
933
+ message.${fieldName} = Array.isArray(object?.${fieldName}) ? [...object.${fieldName}] : [];
844
934
  `);
845
- chunks.push(ts_poet_1.code `}`);
846
935
  }
847
936
  else {
848
937
  // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
@@ -859,13 +948,26 @@ function generateFromJson(ctx, fullName, messageDesc) {
859
948
  `);
860
949
  chunks.push(ts_poet_1.code `}`);
861
950
  }
951
+ else if (types_1.isAnyValueType(field)) {
952
+ chunks.push(ts_poet_1.code `message.${fieldName} = object.${fieldName};`);
953
+ }
954
+ else if (types_1.isStructType(field)) {
955
+ chunks.push(ts_poet_1.code `message.${fieldName} = typeof(object.${fieldName}) === 'object' ? object.${fieldName} : undefined;`);
956
+ }
957
+ else if (types_1.isListValueType(field)) {
958
+ chunks.push(ts_poet_1.code `
959
+ message.${fieldName} = Array.isArray(object?.${fieldName})
960
+ ? ${readSnippet(`object.${fieldName}`)}
961
+ : ${'undefined'};
962
+ `);
963
+ }
862
964
  else {
863
- chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
864
- chunks.push(ts_poet_1.code `message.${fieldName} = ${readSnippet(`object.${fieldName}`)};`);
865
- chunks.push(ts_poet_1.code `} else {`);
866
965
  const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
867
- chunks.push(ts_poet_1.code `message.${fieldName} = ${fallback};`);
868
- chunks.push(ts_poet_1.code `}`);
966
+ chunks.push(ts_poet_1.code `
967
+ message.${fieldName} = (object.${fieldName} !== undefined && object.${fieldName} !== null)
968
+ ? ${readSnippet(`object.${fieldName}`)}
969
+ : ${fallback};
970
+ `);
869
971
  }
870
972
  });
871
973
  // and then wrap up the switch/while/return
@@ -919,14 +1021,23 @@ function generateToJson(ctx, fullName, messageDesc) {
919
1021
  else if (types_1.isTimestamp(valueType) && options.useDate === options_1.DateOption.TIMESTAMP) {
920
1022
  return ts_poet_1.code `${utils.fromTimestamp}(${from}).toISOString()`;
921
1023
  }
1024
+ else if (types_1.isLong(valueType) && options.forceLong === options_1.LongOption.LONG) {
1025
+ return ts_poet_1.code `${from}.toString()`;
1026
+ }
922
1027
  else if (types_1.isScalar(valueType) || types_1.isValueType(ctx, valueType)) {
923
1028
  return ts_poet_1.code `${from}`;
924
1029
  }
1030
+ else if (types_1.isAnyValueType(valueType)) {
1031
+ return ts_poet_1.code `${from}`;
1032
+ }
925
1033
  else {
926
1034
  const type = types_1.basicTypeName(ctx, valueType);
927
1035
  return ts_poet_1.code `${type}.toJSON(${from})`;
928
1036
  }
929
1037
  }
1038
+ else if (types_1.isAnyValueType(field)) {
1039
+ return ts_poet_1.code `${from}`;
1040
+ }
930
1041
  else if (types_1.isMessage(field) && !types_1.isValueType(ctx, field) && !types_1.isMapType(ctx, messageDesc, field)) {
931
1042
  const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
932
1043
  return ts_poet_1.code `${from} ? ${type}.toJSON(${from}) : ${types_1.defaultValue(ctx, field)}`;
@@ -996,7 +1107,10 @@ function generateFromPartial(ctx, fullName, messageDesc) {
996
1107
  messageDesc.field.forEach((field) => {
997
1108
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
998
1109
  const readSnippet = (from) => {
999
- if (types_1.isPrimitive(field) ||
1110
+ if ((types_1.isLong(field) || types_1.isLongValueType(field)) && options.forceLong === options_1.LongOption.LONG) {
1111
+ return ts_poet_1.code `Long.fromValue(${from})`;
1112
+ }
1113
+ else if (types_1.isPrimitive(field) ||
1000
1114
  (types_1.isTimestamp(field) && (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) ||
1001
1115
  types_1.isValueType(ctx, field)) {
1002
1116
  return ts_poet_1.code `${from}`;
@@ -1011,11 +1125,17 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1011
1125
  else if (types_1.isEnum(valueType)) {
1012
1126
  return ts_poet_1.code `${from} as number`;
1013
1127
  }
1128
+ else if (types_1.isLong(valueType) && options.forceLong === options_1.LongOption.LONG) {
1129
+ return ts_poet_1.code `Long.fromValue(${from})`;
1130
+ }
1014
1131
  else {
1015
1132
  const cstr = case_1.capitalize(types_1.basicTypeName(ctx, valueType).toCodeString());
1016
1133
  return ts_poet_1.code `${cstr}(${from})`;
1017
1134
  }
1018
1135
  }
1136
+ else if (types_1.isAnyValueType(valueType)) {
1137
+ return ts_poet_1.code `${from}`;
1138
+ }
1019
1139
  else if (types_1.isTimestamp(valueType) &&
1020
1140
  (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) {
1021
1141
  return ts_poet_1.code `${from}`;
@@ -1028,6 +1148,9 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1028
1148
  return ts_poet_1.code `${type}.fromPartial(${from})`;
1029
1149
  }
1030
1150
  }
1151
+ else if (types_1.isAnyValueType(field)) {
1152
+ return ts_poet_1.code `${from}`;
1153
+ }
1031
1154
  else {
1032
1155
  const type = types_1.basicTypeName(ctx, field);
1033
1156
  return ts_poet_1.code `${type}.fromPartial(${from})`;
@@ -1040,17 +1163,16 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1040
1163
  // and then use the snippet to handle repeated fields if necessary
1041
1164
  if (types_1.isRepeated(field)) {
1042
1165
  if (types_1.isMapType(ctx, messageDesc, field)) {
1043
- chunks.push(ts_poet_1.code `message.${fieldName} = {};`);
1044
- chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1166
+ const fieldType = types_1.toTypeName(ctx, messageDesc, field);
1045
1167
  const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
1046
1168
  chunks.push(ts_poet_1.code `
1047
- Object.entries(object.${fieldName}).forEach(([key, value]) => {
1169
+ message.${fieldName} = Object.entries(object.${fieldName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
1048
1170
  if (value !== undefined) {
1049
- message.${fieldName}[${i}] = ${readSnippet('value')};
1171
+ acc[${i}] = ${readSnippet('value')};
1050
1172
  }
1051
- });
1173
+ return acc;
1174
+ }, {});
1052
1175
  `);
1053
- chunks.push(ts_poet_1.code `}`);
1054
1176
  }
1055
1177
  else {
1056
1178
  chunks.push(ts_poet_1.code `
@@ -1071,30 +1193,18 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1071
1193
  }
1072
1194
  `);
1073
1195
  }
1074
- else if ((types_1.isLong(field) || types_1.isLongValueType(field)) && options.forceLong === options_1.LongOption.LONG) {
1075
- const v = readSnippet(`object.${fieldName}`);
1076
- const type = types_1.basicTypeName(ctx, field);
1077
- chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1078
- chunks.push(ts_poet_1.code `message.${fieldName} = ${v} as ${type};`);
1079
- chunks.push(ts_poet_1.code `} else {`);
1080
- const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1081
- chunks.push(ts_poet_1.code `message.${fieldName} = ${fallback}`);
1082
- chunks.push(ts_poet_1.code `}`);
1083
- }
1084
- else if (types_1.isPrimitive(field) ||
1085
- (types_1.isTimestamp(field) && (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) ||
1086
- types_1.isValueType(ctx, field)) {
1196
+ else if (readSnippet(`x`).toCodeString() == 'x') {
1087
1197
  // An optimized case of the else below that works when `readSnippet` returns the plain input
1088
1198
  const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1089
1199
  chunks.push(ts_poet_1.code `message.${fieldName} = object.${fieldName} ?? ${fallback};`);
1090
1200
  }
1091
1201
  else {
1092
- chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1093
- chunks.push(ts_poet_1.code `message.${fieldName} = ${readSnippet(`object.${fieldName}`)};`);
1094
- chunks.push(ts_poet_1.code `} else {`);
1095
1202
  const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1096
- chunks.push(ts_poet_1.code `message.${fieldName} = ${fallback};`);
1097
- chunks.push(ts_poet_1.code `}`);
1203
+ chunks.push(ts_poet_1.code `
1204
+ message.${fieldName} = (object.${fieldName} !== undefined && object.${fieldName} !== null)
1205
+ ? ${readSnippet(`object.${fieldName}`)}
1206
+ : ${fallback};
1207
+ `);
1098
1208
  }
1099
1209
  });
1100
1210
  // and then wrap up the switch/while/return
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.isBytesValueType = exports.isValueType = exports.isTimestamp = exports.isMapType = 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.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isBytesValueType = exports.isAnyValueType = exports.isValueType = exports.isTimestamp = exports.isMapType = 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;
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");
@@ -359,10 +359,26 @@ function isValueType(ctx, field) {
359
359
  return valueTypeName(ctx, field.typeName) !== undefined;
360
360
  }
361
361
  exports.isValueType = isValueType;
362
+ function isAnyValueType(field) {
363
+ return field.typeName === '.google.protobuf.Value';
364
+ }
365
+ exports.isAnyValueType = isAnyValueType;
362
366
  function isBytesValueType(field) {
363
367
  return field.typeName === '.google.protobuf.BytesValue';
364
368
  }
365
369
  exports.isBytesValueType = isBytesValueType;
370
+ function isListValueType(field) {
371
+ return isListValueTypeName(field.typeName);
372
+ }
373
+ exports.isListValueType = isListValueType;
374
+ function isListValueTypeName(typeName) {
375
+ return typeName === 'google.protobuf.ListValue' || typeName === '.google.protobuf.ListValue';
376
+ }
377
+ exports.isListValueTypeName = isListValueTypeName;
378
+ function isStructType(field) {
379
+ return field.typeName === '.google.protobuf.Struct';
380
+ }
381
+ exports.isStructType = isStructType;
366
382
  function isLongValueType(field) {
367
383
  return field.typeName === '.google.protobuf.Int64Value' || field.typeName === '.google.protobuf.UInt64Value';
368
384
  }
@@ -388,6 +404,12 @@ function valueTypeName(ctx, typeName) {
388
404
  return ts_poet_1.code `boolean`;
389
405
  case '.google.protobuf.BytesValue':
390
406
  return ts_poet_1.code `Uint8Array`;
407
+ case '.google.protobuf.ListValue':
408
+ return ts_poet_1.code `Array<any>`;
409
+ case '.google.protobuf.Value':
410
+ return ts_poet_1.code `any`;
411
+ case '.google.protobuf.Struct':
412
+ return ts_poet_1.code `{[key: string]: any}`;
391
413
  default:
392
414
  return undefined;
393
415
  }
@@ -404,6 +426,7 @@ function wrapperTypeName(typeName) {
404
426
  case '.google.protobuf.UInt64Value':
405
427
  case '.google.protobuf.BoolValue':
406
428
  case '.google.protobuf.BytesValue':
429
+ case '.google.protobuf.ListValue':
407
430
  case '.google.protobuf.Timestamp':
408
431
  return typeName.split('.')[3];
409
432
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.86.0",
3
+ "version": "1.89.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",
@@ -25,9 +25,9 @@
25
25
  "@grpc/proto-loader": "^0.5.6",
26
26
  "@improbable-eng/grpc-web": "^0.14.0",
27
27
  "@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
28
- "@nestjs/common": "^7.6.15",
29
- "@nestjs/core": "^7.6.15",
30
- "@nestjs/microservices": "^7.6.15",
28
+ "@nestjs/common": "^8.2.2",
29
+ "@nestjs/core": "^8.2.2",
30
+ "@nestjs/microservices": "^8.2.2",
31
31
  "@semantic-release/changelog": "^5.0.1",
32
32
  "@semantic-release/commit-analyzer": "^8.0.1",
33
33
  "@semantic-release/git": "^9.0.0",
@@ -36,11 +36,10 @@
36
36
  "@semantic-release/release-notes-generator": "^9.0.2",
37
37
  "@types/jest": "^26.0.22",
38
38
  "@types/node": "^14.14.37",
39
- "grpc": "^1.24.6",
40
39
  "jest": "^26.6.3",
41
40
  "prettier": "^2.2.1",
42
41
  "reflect-metadata": "^0.1.13",
43
- "rxjs": "^6.6.7",
42
+ "rxjs": "^7.4.0",
44
43
  "semantic-release": "^17.4.2",
45
44
  "ts-jest": "^26.5.4",
46
45
  "ts-node": "^9.1.1",