ts-proto 1.134.0 → 1.135.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.markdown +59 -43
- package/build/case.js +11 -1
- package/build/main.js +3 -2
- package/build/utils.js +1 -1
- package/package.json +5 -3
- package/CHANGELOG.md +0 -1246
package/README.markdown
CHANGED
|
@@ -115,7 +115,7 @@ plugins:
|
|
|
115
115
|
out: ../gen/ts
|
|
116
116
|
opt:
|
|
117
117
|
- outputServices=...
|
|
118
|
-
- useExactTypes=...
|
|
118
|
+
- useExactTypes=...
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
# Goals
|
|
@@ -300,33 +300,33 @@ Generated code will be placed in the Gradle build directory.
|
|
|
300
300
|
- With `--ts_proto_opt=useOptionals=messages` (for message fields) or `--ts_proto_opt=useOptionals=all` (for message and scalar fields), fields are declared as optional keys, e.g. `field?: Message` instead of the default `field: Message | undefined`.
|
|
301
301
|
|
|
302
302
|
ts-proto defaults to `useOptionals=none` because it:
|
|
303
|
-
|
|
303
|
+
|
|
304
304
|
1. Prevents typos when initializing messages, and
|
|
305
305
|
2. Provides the most consistent API to readers
|
|
306
|
-
3. Ensures production messages are properly initialized with all fields.
|
|
306
|
+
3. Ensures production messages are properly initialized with all fields.
|
|
307
307
|
|
|
308
|
-
For typo prevention, optional fields make it easy for extra fields to slip into a message (until we get [Exact Types](https://github.com/microsoft/TypeScript/issues/12936)),
|
|
308
|
+
For typo prevention, optional fields make it easy for extra fields to slip into a message (until we get [Exact Types](https://github.com/microsoft/TypeScript/issues/12936)), i.e.:
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
For a consistent API, if `SomeMessage.lastName` is optional `lastName?`, then readers have to check _two_ empty conditions: a) is `lastName` `undefined` (b/c it was created in-memory and left unset), or b) is `lastName` empty string (b/c we read `SomeMessage` off the wire and correctly set `lastName` to empty string)?
|
|
310
|
+
```typescript
|
|
311
|
+
interface SomeMessage {
|
|
312
|
+
firstName: string | undefined;
|
|
313
|
+
lastName: string | undefined;
|
|
314
|
+
}
|
|
315
|
+
// Declared with a typo
|
|
316
|
+
const data = { firstName: "a", lastTypo: "b" };
|
|
317
|
+
// With useOptionals=none, this correctly fails to compile; if `lastName` was optional, it would not
|
|
318
|
+
const message: SomeMessage = { ...data };
|
|
319
|
+
```
|
|
322
320
|
|
|
323
|
-
|
|
321
|
+
For a consistent API, if `SomeMessage.lastName` is optional `lastName?`, then readers have to check _two_ empty conditions: a) is `lastName` `undefined` (b/c it was created in-memory and left unset), or b) is `lastName` empty string (b/c we read `SomeMessage` off the wire and correctly set `lastName` to empty string)?
|
|
324
322
|
|
|
325
|
-
|
|
323
|
+
For ensuring proper initialization, if later `SomeMessage.middleInitial` is added, but it's marked as optional `middleInitial?`, you may have many call sites in production code that _should_ now be passing `middleInitial` to create a valid `SomeMessage`, but are not.
|
|
326
324
|
|
|
327
|
-
|
|
325
|
+
So, between typo-prevention, reader inconsistency, and proper initialization, ts-proto recommends using `useOptionals=none` as the "most safe" option.
|
|
328
326
|
|
|
329
|
-
|
|
327
|
+
All that said, this approach does require writers/creators to set every field (although `fromPartial` is meant to address this), so if you still want to have optional fields, you can set `useOptionals=messages` or `useOptionals=all`.
|
|
328
|
+
|
|
329
|
+
(See [this issue](https://github.com/stephenh/ts-proto/issues/120#issuecomment-678375833) and [this issue](https://github.com/stephenh/ts-proto/issues/397#issuecomment-977259118) for discussions on `useOptional`.)
|
|
330
330
|
|
|
331
331
|
- With `--ts_proto_opt=exportCommonSymbols=false`, utility types like `DeepPartial` won't be `export`d.
|
|
332
332
|
|
|
@@ -346,11 +346,11 @@ Generated code will be placed in the Gradle build directory.
|
|
|
346
346
|
|
|
347
347
|
`snakeToCamel` can also be set as a `_`-delimited list of strings (comma is reserved as the flag delimited), i.e. `--ts_proto_opt=snakeToCamel=keys_json`, where including `keys` will make message keys be camel case and including `json` will make JSON keys be camel case.
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
Empty string, i.e. `snakeToCamel=`, will keep both messages keys and `JSON` keys as snake case (it is the same as `snakeToCamel=false`).
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
Note that to use the `json_name` attribute, you'll have to use the `json`.
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
The default behavior is `keys_json`, i.e. both will be camel cased, and `json_name` will be used if set.
|
|
354
354
|
|
|
355
355
|
- With `--ts_proto_opt=outputEncodeMethods=false`, the `Message.encode` and `Message.decode` methods for working with protobuf-encoded/binary data will not be output.
|
|
356
356
|
|
|
@@ -451,7 +451,7 @@ Generated code will be placed in the Gradle build directory.
|
|
|
451
451
|
- With `--ts_proto_opt=initializeFieldsAsUndefined=false`, all optional field initializers will be omited from the generated base instances.
|
|
452
452
|
|
|
453
453
|
- With `--ts_proto_opt=Mgoogle/protobuf/empty.proto=./google3/protobuf/empty`, ('M' means 'importMapping', similar to [protoc-gen-go](https://developers.google.com/protocol-buffers/docs/reference/go-generated#package)), the generated code import path for `./google/protobuf/empty.ts` will reflect the overridden value:
|
|
454
|
-
|
|
454
|
+
|
|
455
455
|
- `Mfoo/bar.proto=@myorg/some-lib` will map `foo/bar.proto` imports into `import ... from '@myorg/some-lib'`.
|
|
456
456
|
- `Mfoo/bar.proto=./some/local/lib` will map `foo/bar.proto` imports into `import ... from './some/local/lib'`.
|
|
457
457
|
- `Mfoo/bar.proto=some-modules/some-lib` will map `foo/bar.proto` imports into `import ... from 'some-module/some-lib'`.
|
|
@@ -464,9 +464,10 @@ Generated code will be placed in the Gradle build directory.
|
|
|
464
464
|
|
|
465
465
|
- With `--ts_proto_opt=useReadonlyTypes=true`, the generated types will be declared as immutable using typescript's `readonly` modifer.
|
|
466
466
|
|
|
467
|
-
- With `--ts_proto_opt=useSnakeTypeName=false` will remove snake casing from types.
|
|
468
|
-
|
|
467
|
+
- With `--ts_proto_opt=useSnakeTypeName=false` will remove snake casing from types.
|
|
468
|
+
|
|
469
469
|
Example Protobuf
|
|
470
|
+
|
|
470
471
|
```protobuf
|
|
471
472
|
message Box {
|
|
472
473
|
message Element {
|
|
@@ -480,7 +481,8 @@ Generated code will be placed in the Gradle build directory.
|
|
|
480
481
|
}
|
|
481
482
|
}
|
|
482
483
|
```
|
|
483
|
-
|
|
484
|
+
|
|
485
|
+
by default this is enabled which would generate a type of `Box_Element_Image_Alignment`. By disabling this option the type that is generated would be `BoxElementImageAlignment`.
|
|
484
486
|
|
|
485
487
|
### NestJS Support
|
|
486
488
|
|
|
@@ -630,27 +632,41 @@ ts-protoc --ts_proto_out=./output -I=./protos ./protoc/*.proto
|
|
|
630
632
|
|
|
631
633
|
# OneOf Handling
|
|
632
634
|
|
|
633
|
-
By default, `oneof` fields
|
|
635
|
+
By default, ts-proto models `oneof` fields "flatly" in the message, e.g. a message like:
|
|
636
|
+
|
|
637
|
+
```protobuf
|
|
638
|
+
message Foo {
|
|
639
|
+
oneof either_field { string field_a = 1; string field_b = 2; }
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Will generate a `Foo` type with two fields: `field_a: string | undefined;` and `field_b: string | undefined`.
|
|
634
644
|
|
|
635
645
|
With this output, you'll have to check both `if object.field_a` and `if object.field_b`, and if you set one, you'll have to remember to unset the other.
|
|
636
646
|
|
|
637
|
-
|
|
647
|
+
Instead, we recommend using the `oneof=unions` option, which will change the output to be an Abstract Data Type/ADT like:
|
|
638
648
|
|
|
639
649
|
```typescript
|
|
640
650
|
interface YourMessage {
|
|
641
|
-
eitherField
|
|
651
|
+
eitherField?: { $case: "field_a"; field_a: string } | { $case: "field_b"; field_b: string };
|
|
642
652
|
}
|
|
643
653
|
```
|
|
644
654
|
|
|
645
655
|
As this will automatically enforce only one of `field_a` or `field_b` "being set" at a time, because the values are stored in the `eitherField` field that can only have a single value at a time.
|
|
646
656
|
|
|
657
|
+
(Note that `eitherField` is optional b/c `oneof` in Protobuf means "at most one field" is set, and does not mean one of the fields _must_ be set.)
|
|
658
|
+
|
|
647
659
|
In ts-proto's currently-unscheduled 2.x release, `oneof=unions` will become the default behavior.
|
|
648
660
|
|
|
649
661
|
# Default values and unset fields
|
|
650
662
|
|
|
651
|
-
In core Protobuf, values that are _unset_ or equal to the default value are not sent over the wire.
|
|
652
|
-
|
|
653
|
-
|
|
663
|
+
In core Protobuf (and so also `ts-proto`), values that are _unset_ or equal to the default value are not sent over the wire.
|
|
664
|
+
|
|
665
|
+
For example, the default value of a message is `undefined`. Primitive types take their natural default value, e.g. `string` is `''`, `number` is `0`, etc.
|
|
666
|
+
|
|
667
|
+
Protobuf chose/enforces this behavior because it enables forward compatibility, as primitive fields will always have a value, even when omitted by outdated agents.
|
|
668
|
+
|
|
669
|
+
This is good, but it also means _default_ and _unset_ values cannot be distinguished in `ts-proto` fields; it's just fundamentally how Protobuf works.
|
|
654
670
|
|
|
655
671
|
If you need primitive fields where you can detect set/unset, see [Wrapper Types](#wrapper-types).
|
|
656
672
|
|
|
@@ -671,7 +687,7 @@ Foo.decode(protobufBytes); // => { bar: '' }
|
|
|
671
687
|
```
|
|
672
688
|
|
|
673
689
|
```typescript
|
|
674
|
-
Foo.encode({ bar:
|
|
690
|
+
Foo.encode({ bar: "" }); // => { }, writes an empty Foo object, in protobuf binary format
|
|
675
691
|
```
|
|
676
692
|
|
|
677
693
|
**fromJSON / toJSON**
|
|
@@ -680,8 +696,8 @@ Reading JSON will also initialize the default values. Since senders may either o
|
|
|
680
696
|
|
|
681
697
|
```typescript
|
|
682
698
|
Foo.fromJSON({}); // => { bar: '' }
|
|
683
|
-
Foo.fromJSON({ bar:
|
|
684
|
-
Foo.fromJSON({ bar:
|
|
699
|
+
Foo.fromJSON({ bar: "" }); // => { bar: '' }
|
|
700
|
+
Foo.fromJSON({ bar: "baz" }); // => { bar: 'baz' }
|
|
685
701
|
```
|
|
686
702
|
|
|
687
703
|
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.
|
|
@@ -690,16 +706,16 @@ When writing JSON, `ts-proto` currently does **not** normalize message when conv
|
|
|
690
706
|
// Current ts-proto behavior
|
|
691
707
|
Foo.toJSON({}); // => { }
|
|
692
708
|
Foo.toJSON({ bar: undefined }); // => { }
|
|
693
|
-
Foo.toJSON({ bar:
|
|
694
|
-
Foo.toJSON({ bar:
|
|
709
|
+
Foo.toJSON({ bar: "" }); // => { bar: '' } - note: this is the default value, but it's not omitted
|
|
710
|
+
Foo.toJSON({ bar: "baz" }); // => { bar: 'baz' }
|
|
695
711
|
```
|
|
696
712
|
|
|
697
713
|
```typescript
|
|
698
714
|
// Possible future behavior, where ts-proto would normalize message
|
|
699
715
|
Foo.toJSON({}); // => { }
|
|
700
716
|
Foo.toJSON({ bar: undefined }); // => { }
|
|
701
|
-
Foo.toJSON({ bar:
|
|
702
|
-
Foo.toJSON({ bar:
|
|
717
|
+
Foo.toJSON({ bar: "" }); // => { } - note: omitting the default value, as expected
|
|
718
|
+
Foo.toJSON({ bar: "baz" }); // => { bar: 'baz' }
|
|
703
719
|
```
|
|
704
720
|
|
|
705
721
|
- Please open an issue if you need this behavior.
|
|
@@ -755,13 +771,13 @@ interface ExampleMessage {
|
|
|
755
771
|
When encoding a message the primitive value is converted back to its corresponding wrapper type:
|
|
756
772
|
|
|
757
773
|
```typescript
|
|
758
|
-
ExampleMessage.encode({ name:
|
|
774
|
+
ExampleMessage.encode({ name: "foo" }); // => { name: { value: 'foo' } }, in binary
|
|
759
775
|
```
|
|
760
776
|
|
|
761
777
|
When calling toJSON, the value is not converted, because wrapper types are idiomatic in JSON.
|
|
762
778
|
|
|
763
779
|
```typescript
|
|
764
|
-
ExampleMessage.toJSON({ name:
|
|
780
|
+
ExampleMessage.toJSON({ name: "foo" }); // => { name: 'foo' }
|
|
765
781
|
```
|
|
766
782
|
|
|
767
783
|
## JSON Types (Struct Types)
|
|
@@ -803,7 +819,7 @@ interface ExampleMessage {
|
|
|
803
819
|
Encoding a JSON value embedded in a message, converts it to a Struct Type:
|
|
804
820
|
|
|
805
821
|
```typescript
|
|
806
|
-
ExampleMessage.encode({ anything: { name:
|
|
822
|
+
ExampleMessage.encode({ anything: { name: "hello" } });
|
|
807
823
|
/* Outputs the following structure, encoded in protobuf binary format:
|
|
808
824
|
{
|
|
809
825
|
anything: Value {
|
package/build/case.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.camelCase = exports.capitalize = exports.camelToSnake = exports.snakeToCamel = exports.maybeSnakeToCamel = void 0;
|
|
3
|
+
exports.camelCaseGrpc = exports.camelCase = exports.capitalize = exports.camelToSnake = exports.snakeToCamel = exports.maybeSnakeToCamel = void 0;
|
|
4
|
+
const case_anything_1 = require("case-anything");
|
|
4
5
|
/** Converts `key` to TS/JS camel-case idiom, unless overridden not to. */
|
|
5
6
|
function maybeSnakeToCamel(key, options) {
|
|
6
7
|
if (options.snakeToCamel.includes("keys") && key.includes("_")) {
|
|
@@ -35,3 +36,12 @@ function camelCase(s) {
|
|
|
35
36
|
return s.substring(0, 1).toLowerCase() + s.substring(1);
|
|
36
37
|
}
|
|
37
38
|
exports.camelCase = camelCase;
|
|
39
|
+
function camelCaseGrpc(s) {
|
|
40
|
+
/* This function uses the exact same semantics found inside the grpc
|
|
41
|
+
* nodejs library. Camel case splitting must be done by word i.e
|
|
42
|
+
* GetAPIValue must become getApiValue (notice the API becomes Api).
|
|
43
|
+
* This needs to be followed otherwise it will not succeed in the grpc nodejs module.
|
|
44
|
+
*/
|
|
45
|
+
return (0, case_anything_1.camelCase)(s);
|
|
46
|
+
}
|
|
47
|
+
exports.camelCaseGrpc = camelCaseGrpc;
|
package/build/main.js
CHANGED
|
@@ -538,13 +538,14 @@ function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fu
|
|
|
538
538
|
function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) {
|
|
539
539
|
const { options } = ctx;
|
|
540
540
|
const fields = messageDesc.field.filter((field) => (0, types_1.isWithinOneOf)(field) && field.oneofIndex === oneofIndex);
|
|
541
|
+
const mbReadonly = maybeReadonly(options);
|
|
541
542
|
const unionType = (0, ts_poet_1.joinCode)(fields.map((f) => {
|
|
542
543
|
let fieldName = (0, case_1.maybeSnakeToCamel)(f.name, options);
|
|
543
544
|
let typeName = (0, types_1.toTypeName)(ctx, messageDesc, f);
|
|
544
|
-
return (0, ts_poet_1.code) `{ ${
|
|
545
|
+
return (0, ts_poet_1.code) `{ ${mbReadonly}$case: '${fieldName}', ${mbReadonly}${fieldName}: ${typeName} }`;
|
|
545
546
|
}), { on: " | " });
|
|
546
547
|
const name = (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, options);
|
|
547
|
-
return (0, ts_poet_1.code) `${
|
|
548
|
+
return (0, ts_poet_1.code) `${mbReadonly}${name}?: ${unionType},`;
|
|
548
549
|
/*
|
|
549
550
|
// Ideally we'd put the comments for each oneof field next to the anonymous
|
|
550
551
|
// type we've created in the type union above, but ts-poet currently lacks
|
package/build/utils.js
CHANGED
|
@@ -138,7 +138,7 @@ class FormattedMethodDescriptor {
|
|
|
138
138
|
static formatName(methodName, options) {
|
|
139
139
|
let result = methodName;
|
|
140
140
|
if (options.lowerCaseServiceMethods || options.outputServices.includes(options_1.ServiceOption.GRPC)) {
|
|
141
|
-
result = (0, case_1.
|
|
141
|
+
result = (0, case_1.camelCaseGrpc)(result);
|
|
142
142
|
}
|
|
143
143
|
return result;
|
|
144
144
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-proto",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.135.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/plugin.js",
|
|
6
6
|
"repository": "github:stephenh/ts-proto",
|
|
@@ -60,10 +60,12 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@types/object-hash": "^1.3.0",
|
|
63
|
+
"case-anything": "^2.1.10",
|
|
63
64
|
"dataloader": "^1.4.0",
|
|
64
65
|
"object-hash": "^1.3.1",
|
|
65
66
|
"protobufjs": "^6.11.3",
|
|
66
|
-
"ts-poet": "^6.
|
|
67
|
+
"ts-poet": "^6.2.0",
|
|
67
68
|
"ts-proto-descriptors": "1.7.1"
|
|
68
|
-
}
|
|
69
|
+
},
|
|
70
|
+
"packageManager": "yarn@3.3.0"
|
|
69
71
|
}
|