ts-proto 1.89.0 → 1.92.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 +33 -0
- package/README.markdown +60 -7
- package/build/encode.js +14 -4
- package/build/generate-type-registry.js +1 -1
- package/build/main.js +118 -86
- package/build/types.js +13 -4
- package/build/utils.js +10 -1
- package/package.json +12 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
# [1.92.0](https://github.com/stephenh/ts-proto/compare/v1.91.0...v1.92.0) (2021-11-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Use exact types for fromPartial ([#412](https://github.com/stephenh/ts-proto/issues/412)) ([808f8a7](https://github.com/stephenh/ts-proto/commit/808f8a7a77d56f65dd4a4643dd66158f106ab755)), closes [#156](https://github.com/stephenh/ts-proto/issues/156)
|
|
7
|
+
|
|
8
|
+
# [1.91.0](https://github.com/stephenh/ts-proto/compare/v1.90.1...v1.91.0) (2021-11-27)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* use Long.fromValue instead of Long.fromString for improved robustness regarding already parsed objects ([#405](https://github.com/stephenh/ts-proto/issues/405)) ([7bdc3ee](https://github.com/stephenh/ts-proto/commit/7bdc3eee05ed1318e18e27aa7d5bb2680060f8b6))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* Include dockerized protoc ([#404](https://github.com/stephenh/ts-proto/issues/404)) ([7564a78](https://github.com/stephenh/ts-proto/commit/7564a7887ccd0bb80cac19d313ee9bd8daae778d))
|
|
19
|
+
|
|
20
|
+
## [1.90.1](https://github.com/stephenh/ts-proto/compare/v1.90.0...v1.90.1) (2021-11-27)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* code-generation for Services with Struct response types ([#407](https://github.com/stephenh/ts-proto/issues/407)) ([f041fa1](https://github.com/stephenh/ts-proto/commit/f041fa1047816748c366bcb81895b6b917eb328d))
|
|
26
|
+
|
|
27
|
+
# [1.90.0](https://github.com/stephenh/ts-proto/compare/v1.89.0...v1.90.0) (2021-11-24)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
* Add support for 'json_name' annotation ([#408](https://github.com/stephenh/ts-proto/issues/408)) ([b519717](https://github.com/stephenh/ts-proto/commit/b5197174bcaacb8f163cd197d52ab9c645d21d4c))
|
|
33
|
+
|
|
1
34
|
# [1.89.0](https://github.com/stephenh/ts-proto/compare/v1.88.0...v1.89.0) (2021-11-24)
|
|
2
35
|
|
|
3
36
|
|
package/README.markdown
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- [Supported options](#supported-options)
|
|
19
19
|
- [Only Types](#only-types)
|
|
20
20
|
- [NestJS Support](NESTJS.markdown)
|
|
21
|
-
- [
|
|
21
|
+
- [Development](#development)
|
|
22
22
|
- [Assumptions](#assumptions)
|
|
23
23
|
- [Todo](#todo)
|
|
24
24
|
- [OneOf Handling](#oneof-handling)
|
|
@@ -362,15 +362,69 @@ Kudos to our sponsors:
|
|
|
362
362
|
|
|
363
363
|
If you need ts-proto customizations or priority support for your company, you can ping me at [via email](mailto:stephen.haberman@gmail.com).
|
|
364
364
|
|
|
365
|
-
#
|
|
365
|
+
# Development
|
|
366
366
|
|
|
367
|
-
|
|
367
|
+
**Requirements**
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
- [Docker](https://www.docker.com) or [protoc](https://github.com/protocolbuffers/protobuf/releases) v3.19.1
|
|
370
|
+
- `yarn` — `npm install -g yarn`
|
|
370
371
|
|
|
371
|
-
|
|
372
|
+
**Setup**
|
|
372
373
|
|
|
373
|
-
The
|
|
374
|
+
The commands below assume you have **Docker** installed. To use a **local** copy of `protoc` without docker, use commands suffixed with `:local`
|
|
375
|
+
|
|
376
|
+
- Check out the [repository]() for the latest code.
|
|
377
|
+
- Run `yarn install` to install the dependencies.
|
|
378
|
+
- Run `yarn build:test` or `yarn build:test:local` to generate the test files.
|
|
379
|
+
> _This runs the following commands:_
|
|
380
|
+
> - `proto2bin` — Converts integration test `.proto` files to `.bin`.
|
|
381
|
+
> - `bin2ts` — Runs `ts-proto` on the `.bin` files to generate `.ts` files.
|
|
382
|
+
> - `proto2pbjs` — Generates a reference implementation using `pbjs` for testing compatibility.
|
|
383
|
+
- Run `yarn test`
|
|
384
|
+
|
|
385
|
+
**Workflow**
|
|
386
|
+
|
|
387
|
+
- Modifying the plugin implementation:
|
|
388
|
+
- Run `yarn bin2ts` or `yarn bin2ts:local`.
|
|
389
|
+
_Since the proto files were not changed, you only need to regenerate the typescript files._
|
|
390
|
+
- Run `yarn test` to verify the typescript files are compatible with the reference implementation, and pass other tests.
|
|
391
|
+
- Updating or adding `.proto` files in the integration directory:
|
|
392
|
+
- Run `yarn build:test` to regenerate the integration test files.
|
|
393
|
+
- Run `yarn test` to retest.
|
|
394
|
+
|
|
395
|
+
**Contributing**
|
|
396
|
+
|
|
397
|
+
- Run `yarn build:test` and `yarn test` to make sure everything works.
|
|
398
|
+
- Run `yarn prettier` to format the typescript files.
|
|
399
|
+
- Commit the changes:
|
|
400
|
+
- Also include the generated `.bin` files for the tests where you added or modified `.proto` files.
|
|
401
|
+
> These are checked into git so that the test suite can run without having to invoke the `protoc` build chain.
|
|
402
|
+
- Also include the generated `.ts` files.
|
|
403
|
+
- Create a pull request
|
|
404
|
+
|
|
405
|
+
**Dockerized Protoc**
|
|
406
|
+
|
|
407
|
+
The repository includes a dockerized version of `protoc`, which is configured in [docker-compose.yml](docker-compose.yml).
|
|
408
|
+
|
|
409
|
+
It can be useful in case you want to manually invoke the plugin with a known version of `protoc`.
|
|
410
|
+
|
|
411
|
+
Usage:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# Include the protoc alias in your shell.
|
|
415
|
+
. aliases.sh
|
|
416
|
+
|
|
417
|
+
# Run protoc as usual. The ts-proto directory is available in /ts-proto.
|
|
418
|
+
protoc --plugin=/ts-proto/protoc-gen-ts_proto --ts_proto_out=./output -I=./protos ./protoc/*.proto
|
|
419
|
+
|
|
420
|
+
# Or use the ts-protoc alias which specifies the plugin path for you.
|
|
421
|
+
ts-protoc --ts_proto_out=./output -I=./protos ./protoc/*.proto
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
- All paths must be relative paths _within_ the current working directory of the host. `../` is not allowed
|
|
425
|
+
- Within the docker container, the absolute path to the project root is `/ts-proto`
|
|
426
|
+
- The container mounts the current working directory in `/host`, and sets it as its working directory.
|
|
427
|
+
- Once `aliases.sh` is sourced, you can use the `protoc` command in any folder.
|
|
374
428
|
|
|
375
429
|
# Assumptions
|
|
376
430
|
|
|
@@ -379,7 +433,6 @@ The test suite's proto files (i.e. `simple.proto`, `batching.proto`, etc.) curre
|
|
|
379
433
|
# Todo
|
|
380
434
|
|
|
381
435
|
- Support the string-based encoding of duration in `fromJSON`/`toJSON`
|
|
382
|
-
- Support the `json_name` annotation
|
|
383
436
|
- Make `oneof=unions` the default behavior in 2.0
|
|
384
437
|
- Probably change `forceLong` default in 2.0, should default to `forceLong=long`
|
|
385
438
|
- Make `esModuleInterop=true` the default in 2.0
|
package/build/encode.js
CHANGED
|
@@ -7,12 +7,20 @@ const options_1 = require("./options");
|
|
|
7
7
|
function generateEncoder(ctx, typeName) {
|
|
8
8
|
const name = types_1.wrapperTypeName(typeName);
|
|
9
9
|
if (!name) {
|
|
10
|
-
return ts_poet_1.code `${types_1.messageToTypeName(ctx, typeName)}.encode(value).finish()`;
|
|
10
|
+
return ts_poet_1.code `${types_1.messageToTypeName(ctx, typeName, { keepValueType: true })}.encode(value).finish()`;
|
|
11
11
|
}
|
|
12
12
|
if (name == 'Timestamp') {
|
|
13
13
|
const TimestampValue = ts_poet_1.imp(`${name}@./google/protobuf/timestamp`);
|
|
14
14
|
return ts_poet_1.code `${TimestampValue}.encode(${ctx.utils.toTimestamp}(value)).finish()`;
|
|
15
15
|
}
|
|
16
|
+
if (name == 'Struct') {
|
|
17
|
+
const StructType = ts_poet_1.imp(`${name}@./google/protobuf/struct`);
|
|
18
|
+
return ts_poet_1.code `${StructType}.encode(${StructType}.wrap(value)).finish()`;
|
|
19
|
+
}
|
|
20
|
+
if (name == 'ListValue') {
|
|
21
|
+
const ListValueType = ts_poet_1.imp(`${name}@./google/protobuf/struct`);
|
|
22
|
+
return ts_poet_1.code `${ListValueType}.encode({values: value ?? []}).finish()`;
|
|
23
|
+
}
|
|
16
24
|
const TypeValue = ts_poet_1.imp(`${name}@./google/protobuf/wrappers`);
|
|
17
25
|
switch (name) {
|
|
18
26
|
case 'StringValue':
|
|
@@ -32,8 +40,6 @@ function generateEncoder(ctx, typeName) {
|
|
|
32
40
|
return ts_poet_1.code `${TypeValue}.encode({value: value ?? false}).finish()`;
|
|
33
41
|
case 'BytesValue':
|
|
34
42
|
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()`;
|
|
37
43
|
}
|
|
38
44
|
throw new Error(`unknown wrapper type: ${name}`);
|
|
39
45
|
}
|
|
@@ -41,13 +47,17 @@ exports.generateEncoder = generateEncoder;
|
|
|
41
47
|
function generateDecoder(ctx, typeName) {
|
|
42
48
|
let name = types_1.wrapperTypeName(typeName);
|
|
43
49
|
if (!name) {
|
|
44
|
-
return ts_poet_1.code `${types_1.messageToTypeName(ctx, typeName)}.decode(value)`;
|
|
50
|
+
return ts_poet_1.code `${types_1.messageToTypeName(ctx, typeName, { keepValueType: true })}.decode(value)`;
|
|
45
51
|
}
|
|
46
52
|
let TypeValue;
|
|
47
53
|
if (name == 'Timestamp') {
|
|
48
54
|
TypeValue = ts_poet_1.imp(`${name}@./google/protobuf/timestamp`);
|
|
49
55
|
return ts_poet_1.code `${TypeValue}.decode(value)`;
|
|
50
56
|
}
|
|
57
|
+
if (name == 'Struct' || name == 'ListValue') {
|
|
58
|
+
TypeValue = ts_poet_1.imp(`${name}@./google/protobuf/struct`);
|
|
59
|
+
return ts_poet_1.code `${TypeValue}.unwrap(${TypeValue}.decode(value))`;
|
|
60
|
+
}
|
|
51
61
|
TypeValue = ts_poet_1.imp(`${name}@./google/protobuf/wrappers`);
|
|
52
62
|
return ts_poet_1.code `${TypeValue}.decode(value).value`;
|
|
53
63
|
}
|
|
@@ -13,7 +13,7 @@ function generateTypeRegistry(ctx) {
|
|
|
13
13
|
chunks.push(ts_poet_1.code `
|
|
14
14
|
export const messageTypeRegistry = new Map<string, MessageType>();
|
|
15
15
|
`);
|
|
16
|
-
chunks.push(ts_poet_1.code
|
|
16
|
+
chunks.push(ts_poet_1.code ` ${ctx.utils.Builtin.ifUsed} ${ctx.utils.DeepPartial.ifUsed}`);
|
|
17
17
|
return ts_poet_1.joinCode(chunks, { on: '\n\n' });
|
|
18
18
|
}
|
|
19
19
|
exports.generateTypeRegistry = generateTypeRegistry;
|
package/build/main.js
CHANGED
|
@@ -77,6 +77,8 @@ function generateFile(ctx, fileDesc) {
|
|
|
77
77
|
if (options.outputPartialMethods) {
|
|
78
78
|
staticMembers.push(generateFromPartial(ctx, fullName, message));
|
|
79
79
|
}
|
|
80
|
+
staticMembers.push(...generateWrap(fullTypeName));
|
|
81
|
+
staticMembers.push(...generateUnwrap(fullTypeName));
|
|
80
82
|
chunks.push(ts_poet_1.code `
|
|
81
83
|
export const ${ts_poet_1.def(fullName)} = {
|
|
82
84
|
${ts_poet_1.joinCode(staticMembers, { on: ',\n\n' })}
|
|
@@ -184,7 +186,6 @@ function makeUtils(options) {
|
|
|
184
186
|
...makeDeepPartial(options, longs),
|
|
185
187
|
...makeTimestampMethods(options, longs),
|
|
186
188
|
...longs,
|
|
187
|
-
...makeWrappingUtils(),
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
exports.makeUtils = makeUtils;
|
|
@@ -241,57 +242,6 @@ function makeLongUtils(options, bytes) {
|
|
|
241
242
|
`);
|
|
242
243
|
return { numberToLong, longToNumber, longToString, longInit, Long };
|
|
243
244
|
}
|
|
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
|
-
}
|
|
295
245
|
function makeByteUtils() {
|
|
296
246
|
const globalThis = ts_poet_1.conditionalOutput('globalThis', ts_poet_1.code `
|
|
297
247
|
declare var self: any | undefined;
|
|
@@ -339,11 +289,20 @@ function makeDeepPartial(options, longs) {
|
|
|
339
289
|
const maybeExport = options.exportCommonSymbols ? 'export' : '';
|
|
340
290
|
// Allow passing longs as numbers or strings, nad we'll convert them
|
|
341
291
|
const maybeLong = options.forceLong === options_1.LongOption.LONG ? ts_poet_1.code ` : T extends ${longs.Long} ? string | number | Long ` : '';
|
|
342
|
-
const
|
|
292
|
+
const Builtin = ts_poet_1.conditionalOutput('Builtin', ts_poet_1.code `type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;`);
|
|
293
|
+
// Based on https://github.com/sindresorhus/type-fest/pull/259
|
|
294
|
+
const maybeExcludeType = options.outputTypeRegistry ? `| '$type'` : '';
|
|
295
|
+
const Exact = ts_poet_1.conditionalOutput('Exact', ts_poet_1.code `
|
|
296
|
+
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
|
297
|
+
${maybeExport} type Exact<P, I extends P> = P extends ${Builtin}
|
|
298
|
+
? P
|
|
299
|
+
: P &
|
|
300
|
+
{ [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P> ${maybeExcludeType}>, never>;
|
|
301
|
+
`);
|
|
343
302
|
// Based on the type from ts-essentials
|
|
303
|
+
const keys = options.outputTypeRegistry ? ts_poet_1.code `Exclude<keyof T, '$type'>` : ts_poet_1.code `keyof T`;
|
|
344
304
|
const DeepPartial = ts_poet_1.conditionalOutput('DeepPartial', ts_poet_1.code `
|
|
345
|
-
type
|
|
346
|
-
${maybeExport} type DeepPartial<T> = T extends Builtin
|
|
305
|
+
${maybeExport} type DeepPartial<T> = T extends ${Builtin}
|
|
347
306
|
? T
|
|
348
307
|
${maybeLong}
|
|
349
308
|
: T extends Array<infer U>
|
|
@@ -354,7 +313,7 @@ function makeDeepPartial(options, longs) {
|
|
|
354
313
|
? { [K in ${keys}]?: DeepPartial<T[K]> }
|
|
355
314
|
: Partial<T>;
|
|
356
315
|
`);
|
|
357
|
-
return { DeepPartial };
|
|
316
|
+
return { Builtin, DeepPartial, Exact };
|
|
358
317
|
}
|
|
359
318
|
function makeTimestampMethods(options, longs) {
|
|
360
319
|
const Timestamp = ts_poet_1.imp('Timestamp@./google/protobuf/timestamp');
|
|
@@ -580,16 +539,13 @@ function generateDecode(ctx, fullName, messageDesc) {
|
|
|
580
539
|
}
|
|
581
540
|
}
|
|
582
541
|
else if (types_1.isValueType(ctx, field)) {
|
|
542
|
+
const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
|
|
583
543
|
const unwrap = (decodedValue) => {
|
|
584
|
-
if (types_1.isAnyValueType(field))
|
|
585
|
-
return ts_poet_1.code `${
|
|
586
|
-
|
|
587
|
-
return ts_poet_1.code `${ctx.utils.unwrapStruct}(${decodedValue})`;
|
|
588
|
-
if (types_1.isListValueType(field))
|
|
589
|
-
return ts_poet_1.code `${decodedValue}.values`;
|
|
544
|
+
if (types_1.isListValueType(field) || types_1.isStructType(field) || types_1.isAnyValueType(field)) {
|
|
545
|
+
return ts_poet_1.code `${type}.unwrap(${decodedValue})`;
|
|
546
|
+
}
|
|
590
547
|
return ts_poet_1.code `${decodedValue}.value`;
|
|
591
548
|
};
|
|
592
|
-
const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
|
|
593
549
|
const decoder = ts_poet_1.code `${type}.decode(reader, reader.uint32())`;
|
|
594
550
|
readSnippet = ts_poet_1.code `${unwrap(decoder)}`;
|
|
595
551
|
}
|
|
@@ -687,17 +643,14 @@ function generateEncode(ctx, fullName, messageDesc) {
|
|
|
687
643
|
}
|
|
688
644
|
else if (types_1.isValueType(ctx, field)) {
|
|
689
645
|
const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : '';
|
|
646
|
+
const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
|
|
690
647
|
const wrappedValue = (place) => {
|
|
691
|
-
if (types_1.isAnyValueType(field))
|
|
692
|
-
return ts_poet_1.code `${
|
|
693
|
-
|
|
694
|
-
return ts_poet_1.code `{values: ${place}}`;
|
|
695
|
-
if (types_1.isStructType(field))
|
|
696
|
-
return ts_poet_1.code `${ctx.utils.wrapStruct}(${place})`;
|
|
648
|
+
if (types_1.isAnyValueType(field) || types_1.isListValueType(field) || types_1.isStructType(field)) {
|
|
649
|
+
return ts_poet_1.code `${type}.wrap(${place})`;
|
|
650
|
+
}
|
|
697
651
|
return ts_poet_1.code `{${maybeTypeField} value: ${place}!}`;
|
|
698
652
|
};
|
|
699
653
|
const tag = ((field.number << 3) | 2) >>> 0;
|
|
700
|
-
const type = types_1.basicTypeName(ctx, field, { keepValueType: true });
|
|
701
654
|
writeSnippet = (place) => ts_poet_1.code `${type}.encode(${wrappedValue(place)}, writer.uint32(${tag}).fork()).ldelim()`;
|
|
702
655
|
}
|
|
703
656
|
else if (types_1.isMessage(field)) {
|
|
@@ -814,6 +767,7 @@ function generateFromJson(ctx, fullName, messageDesc) {
|
|
|
814
767
|
// add a check for each incoming field
|
|
815
768
|
messageDesc.field.forEach((field) => {
|
|
816
769
|
const fieldName = case_1.maybeSnakeToCamel(field.name, options);
|
|
770
|
+
const jsonName = utils_1.determineFieldJsonName(field, options);
|
|
817
771
|
// get a generic 'reader.doSomething' bit that is specific to the basic type
|
|
818
772
|
const readSnippet = (from) => {
|
|
819
773
|
if (types_1.isEnum(field)) {
|
|
@@ -878,7 +832,7 @@ function generateFromJson(ctx, fullName, messageDesc) {
|
|
|
878
832
|
}
|
|
879
833
|
}
|
|
880
834
|
else if (types_1.isLong(valueType) && options.forceLong === options_1.LongOption.LONG) {
|
|
881
|
-
return ts_poet_1.code `Long.
|
|
835
|
+
return ts_poet_1.code `Long.fromValue(${from} as Long | string)`;
|
|
882
836
|
}
|
|
883
837
|
else if (types_1.isEnum(valueType)) {
|
|
884
838
|
return ts_poet_1.code `${from} as number`;
|
|
@@ -922,7 +876,7 @@ function generateFromJson(ctx, fullName, messageDesc) {
|
|
|
922
876
|
const fieldType = types_1.toTypeName(ctx, messageDesc, field);
|
|
923
877
|
const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
|
|
924
878
|
chunks.push(ts_poet_1.code `
|
|
925
|
-
message.${fieldName} = Object.entries(object.${
|
|
879
|
+
message.${fieldName} = Object.entries(object.${jsonName} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
|
|
926
880
|
acc[${i}] = ${readSnippet('value')};
|
|
927
881
|
return acc;
|
|
928
882
|
}, {});
|
|
@@ -930,21 +884,21 @@ function generateFromJson(ctx, fullName, messageDesc) {
|
|
|
930
884
|
}
|
|
931
885
|
else if (types_1.isAnyValueType(field)) {
|
|
932
886
|
chunks.push(ts_poet_1.code `
|
|
933
|
-
message.${fieldName} = Array.isArray(object?.${
|
|
887
|
+
message.${fieldName} = Array.isArray(object?.${jsonName}) ? [...object.${jsonName}] : [];
|
|
934
888
|
`);
|
|
935
889
|
}
|
|
936
890
|
else {
|
|
937
891
|
// Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
|
|
938
892
|
chunks.push(ts_poet_1.code `
|
|
939
|
-
message.${fieldName} = (object.${
|
|
893
|
+
message.${fieldName} = (object.${jsonName} ?? []).map((e: any) => ${readSnippet('e')});
|
|
940
894
|
`);
|
|
941
895
|
}
|
|
942
896
|
}
|
|
943
897
|
else if (types_1.isWithinOneOfThatShouldBeUnion(options, field)) {
|
|
944
|
-
chunks.push(ts_poet_1.code `if (object.${
|
|
898
|
+
chunks.push(ts_poet_1.code `if (object.${jsonName} !== undefined && object.${jsonName} !== null) {`);
|
|
945
899
|
const oneofName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
|
|
946
900
|
chunks.push(ts_poet_1.code `
|
|
947
|
-
message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${
|
|
901
|
+
message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${jsonName}`)} }
|
|
948
902
|
`);
|
|
949
903
|
chunks.push(ts_poet_1.code `}`);
|
|
950
904
|
}
|
|
@@ -964,8 +918,8 @@ function generateFromJson(ctx, fullName, messageDesc) {
|
|
|
964
918
|
else {
|
|
965
919
|
const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
|
|
966
920
|
chunks.push(ts_poet_1.code `
|
|
967
|
-
message.${fieldName} = (object.${
|
|
968
|
-
? ${readSnippet(`object.${
|
|
921
|
+
message.${fieldName} = (object.${jsonName} !== undefined && object.${jsonName} !== null)
|
|
922
|
+
? ${readSnippet(`object.${jsonName}`)}
|
|
969
923
|
: ${fallback};
|
|
970
924
|
`);
|
|
971
925
|
}
|
|
@@ -986,6 +940,7 @@ function generateToJson(ctx, fullName, messageDesc) {
|
|
|
986
940
|
// then add a case for each field
|
|
987
941
|
messageDesc.field.forEach((field) => {
|
|
988
942
|
const fieldName = case_1.maybeSnakeToCamel(field.name, options);
|
|
943
|
+
const jsonName = utils_1.determineFieldJsonName(field, options);
|
|
989
944
|
const readSnippet = (from) => {
|
|
990
945
|
if (types_1.isEnum(field)) {
|
|
991
946
|
const toJson = types_1.getEnumMethod(typeMap, field.typeName, 'ToJSON');
|
|
@@ -1061,10 +1016,10 @@ function generateToJson(ctx, fullName, messageDesc) {
|
|
|
1061
1016
|
if (types_1.isMapType(ctx, messageDesc, field)) {
|
|
1062
1017
|
// Maps might need their values transformed, i.e. bytes --> base64
|
|
1063
1018
|
chunks.push(ts_poet_1.code `
|
|
1064
|
-
obj.${
|
|
1019
|
+
obj.${jsonName} = {};
|
|
1065
1020
|
if (message.${fieldName}) {
|
|
1066
1021
|
Object.entries(message.${fieldName}).forEach(([k, v]) => {
|
|
1067
|
-
obj.${
|
|
1022
|
+
obj.${jsonName}[k] = ${readSnippet('v')};
|
|
1068
1023
|
});
|
|
1069
1024
|
}
|
|
1070
1025
|
`);
|
|
@@ -1073,9 +1028,9 @@ function generateToJson(ctx, fullName, messageDesc) {
|
|
|
1073
1028
|
// Arrays might need their elements transformed
|
|
1074
1029
|
chunks.push(ts_poet_1.code `
|
|
1075
1030
|
if (message.${fieldName}) {
|
|
1076
|
-
obj.${
|
|
1031
|
+
obj.${jsonName} = message.${fieldName}.map(e => ${readSnippet('e')});
|
|
1077
1032
|
} else {
|
|
1078
|
-
obj.${
|
|
1033
|
+
obj.${jsonName} = [];
|
|
1079
1034
|
}
|
|
1080
1035
|
`);
|
|
1081
1036
|
}
|
|
@@ -1083,11 +1038,11 @@ function generateToJson(ctx, fullName, messageDesc) {
|
|
|
1083
1038
|
// oneofs in a union are only output as `oneof name = ...`
|
|
1084
1039
|
const oneofName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
|
|
1085
1040
|
const v = readSnippet(`message.${oneofName}?.${fieldName}`);
|
|
1086
|
-
chunks.push(ts_poet_1.code `message.${oneofName}?.$case === '${fieldName}' && (obj.${
|
|
1041
|
+
chunks.push(ts_poet_1.code `message.${oneofName}?.$case === '${fieldName}' && (obj.${jsonName} = ${v});`);
|
|
1087
1042
|
}
|
|
1088
1043
|
else {
|
|
1089
1044
|
const v = readSnippet(`message.${fieldName}`);
|
|
1090
|
-
chunks.push(ts_poet_1.code `message.${fieldName} !== undefined && (obj.${
|
|
1045
|
+
chunks.push(ts_poet_1.code `message.${fieldName} !== undefined && (obj.${jsonName} = ${v});`);
|
|
1091
1046
|
}
|
|
1092
1047
|
});
|
|
1093
1048
|
chunks.push(ts_poet_1.code `return obj;`);
|
|
@@ -1099,8 +1054,9 @@ function generateFromPartial(ctx, fullName, messageDesc) {
|
|
|
1099
1054
|
const chunks = [];
|
|
1100
1055
|
const Timestamp = ts_poet_1.imp('Timestamp@./google/protobuf/timestamp');
|
|
1101
1056
|
// create the basic function declaration
|
|
1057
|
+
const paramName = messageDesc.field.length > 0 ? 'object' : '_';
|
|
1102
1058
|
chunks.push(ts_poet_1.code `
|
|
1103
|
-
fromPartial
|
|
1059
|
+
fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
|
|
1104
1060
|
const message = { ...base${fullName} } as ${fullName};
|
|
1105
1061
|
`);
|
|
1106
1062
|
// add a check for each incoming field
|
|
@@ -1176,7 +1132,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
|
|
|
1176
1132
|
}
|
|
1177
1133
|
else {
|
|
1178
1134
|
chunks.push(ts_poet_1.code `
|
|
1179
|
-
message.${fieldName} =
|
|
1135
|
+
message.${fieldName} = object.${fieldName}?.map((e) => ${readSnippet('e')}) || [];
|
|
1180
1136
|
`);
|
|
1181
1137
|
}
|
|
1182
1138
|
}
|
|
@@ -1212,6 +1168,82 @@ function generateFromPartial(ctx, fullName, messageDesc) {
|
|
|
1212
1168
|
chunks.push(ts_poet_1.code `}`);
|
|
1213
1169
|
return ts_poet_1.joinCode(chunks, { on: '\n' });
|
|
1214
1170
|
}
|
|
1171
|
+
function generateWrap(fullProtoTypeName) {
|
|
1172
|
+
const chunks = [];
|
|
1173
|
+
if (types_1.isStructTypeName(fullProtoTypeName)) {
|
|
1174
|
+
chunks.push(ts_poet_1.code `wrap(object: {[key: string]: any} | undefined): Struct {
|
|
1175
|
+
const struct = Struct.fromPartial({});
|
|
1176
|
+
if (object !== undefined) {
|
|
1177
|
+
Object.keys(object).forEach(key => {
|
|
1178
|
+
struct.fields[key] = object[key];
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
return struct;
|
|
1182
|
+
}`);
|
|
1183
|
+
}
|
|
1184
|
+
if (types_1.isAnyValueTypeName(fullProtoTypeName)) {
|
|
1185
|
+
chunks.push(ts_poet_1.code `wrap(value: any): Value {
|
|
1186
|
+
if (value === null) {
|
|
1187
|
+
return {nullValue: 0} as Value;
|
|
1188
|
+
} else if (typeof value === 'boolean') {
|
|
1189
|
+
return {boolValue: value} as Value;
|
|
1190
|
+
} else if (typeof value === 'number') {
|
|
1191
|
+
return {numberValue: value} as Value;
|
|
1192
|
+
} else if (typeof value === 'string') {
|
|
1193
|
+
return {stringValue: value} as Value;
|
|
1194
|
+
} else if (Array.isArray(value)) {
|
|
1195
|
+
return {listValue: value} as Value;
|
|
1196
|
+
} else if (typeof value === 'object') {
|
|
1197
|
+
return {structValue: value} as Value;
|
|
1198
|
+
} else if (typeof value === 'undefined') {
|
|
1199
|
+
return {} as Value;
|
|
1200
|
+
} else {
|
|
1201
|
+
throw new Error('Unsupported any value type: ' + typeof value);
|
|
1202
|
+
}
|
|
1203
|
+
}`);
|
|
1204
|
+
}
|
|
1205
|
+
if (types_1.isListValueTypeName(fullProtoTypeName)) {
|
|
1206
|
+
chunks.push(ts_poet_1.code `wrap(value: Array<any>): ListValue {
|
|
1207
|
+
return {values: value};
|
|
1208
|
+
}`);
|
|
1209
|
+
}
|
|
1210
|
+
return chunks;
|
|
1211
|
+
}
|
|
1212
|
+
function generateUnwrap(fullProtoTypeName) {
|
|
1213
|
+
const chunks = [];
|
|
1214
|
+
if (types_1.isStructTypeName(fullProtoTypeName)) {
|
|
1215
|
+
chunks.push(ts_poet_1.code `unwrap(message: Struct): {[key: string]: any} {
|
|
1216
|
+
const object: { [key: string]: any } = {};
|
|
1217
|
+
Object.keys(message.fields).forEach(key => {
|
|
1218
|
+
object[key] = message.fields[key];
|
|
1219
|
+
});
|
|
1220
|
+
return object;
|
|
1221
|
+
}`);
|
|
1222
|
+
}
|
|
1223
|
+
if (types_1.isAnyValueTypeName(fullProtoTypeName)) {
|
|
1224
|
+
chunks.push(ts_poet_1.code `unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
|
|
1225
|
+
if (message.stringValue !== undefined) {
|
|
1226
|
+
return message.stringValue;
|
|
1227
|
+
} else if (message.numberValue !== undefined) {
|
|
1228
|
+
return message.numberValue;
|
|
1229
|
+
} else if (message.boolValue !== undefined) {
|
|
1230
|
+
return message.boolValue;
|
|
1231
|
+
} else if (message.structValue !== undefined) {
|
|
1232
|
+
return message.structValue;
|
|
1233
|
+
} else if (message.listValue !== undefined) {
|
|
1234
|
+
return message.listValue;
|
|
1235
|
+
} else if (message.nullValue !== undefined) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
}`);
|
|
1239
|
+
}
|
|
1240
|
+
if (types_1.isListValueTypeName(fullProtoTypeName)) {
|
|
1241
|
+
chunks.push(ts_poet_1.code `unwrap(message: ListValue): Array<any> {
|
|
1242
|
+
return message.values;
|
|
1243
|
+
}`);
|
|
1244
|
+
}
|
|
1245
|
+
return chunks;
|
|
1246
|
+
}
|
|
1215
1247
|
exports.contextTypeVar = 'Context extends DataLoaders';
|
|
1216
1248
|
function maybeCastToNumber(ctx, messageDesc, field, variableName) {
|
|
1217
1249
|
const { keyType } = types_1.detectMapType(ctx, messageDesc, field);
|
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.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;
|
|
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.isStructTypeName = exports.isStructType = exports.isListValueTypeName = exports.isListValueType = exports.isBytesValueType = exports.isAnyValueTypeName = 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");
|
|
@@ -360,9 +360,13 @@ function isValueType(ctx, field) {
|
|
|
360
360
|
}
|
|
361
361
|
exports.isValueType = isValueType;
|
|
362
362
|
function isAnyValueType(field) {
|
|
363
|
-
return field.typeName
|
|
363
|
+
return isAnyValueTypeName(field.typeName);
|
|
364
364
|
}
|
|
365
365
|
exports.isAnyValueType = isAnyValueType;
|
|
366
|
+
function isAnyValueTypeName(typeName) {
|
|
367
|
+
return typeName === 'google.protobuf.Value' || typeName === '.google.protobuf.Value';
|
|
368
|
+
}
|
|
369
|
+
exports.isAnyValueTypeName = isAnyValueTypeName;
|
|
366
370
|
function isBytesValueType(field) {
|
|
367
371
|
return field.typeName === '.google.protobuf.BytesValue';
|
|
368
372
|
}
|
|
@@ -376,9 +380,13 @@ function isListValueTypeName(typeName) {
|
|
|
376
380
|
}
|
|
377
381
|
exports.isListValueTypeName = isListValueTypeName;
|
|
378
382
|
function isStructType(field) {
|
|
379
|
-
return field.typeName
|
|
383
|
+
return isStructTypeName(field.typeName);
|
|
380
384
|
}
|
|
381
385
|
exports.isStructType = isStructType;
|
|
386
|
+
function isStructTypeName(typeName) {
|
|
387
|
+
return typeName === 'google.protobuf.Struct' || typeName === '.google.protobuf.Struct';
|
|
388
|
+
}
|
|
389
|
+
exports.isStructTypeName = isStructTypeName;
|
|
382
390
|
function isLongValueType(field) {
|
|
383
391
|
return field.typeName === '.google.protobuf.Int64Value' || field.typeName === '.google.protobuf.UInt64Value';
|
|
384
392
|
}
|
|
@@ -428,6 +436,7 @@ function wrapperTypeName(typeName) {
|
|
|
428
436
|
case '.google.protobuf.BytesValue':
|
|
429
437
|
case '.google.protobuf.ListValue':
|
|
430
438
|
case '.google.protobuf.Timestamp':
|
|
439
|
+
case '.google.protobuf.Struct':
|
|
431
440
|
return typeName.split('.')[3];
|
|
432
441
|
default:
|
|
433
442
|
return undefined;
|
|
@@ -548,7 +557,7 @@ function requestType(ctx, methodDesc) {
|
|
|
548
557
|
}
|
|
549
558
|
exports.requestType = requestType;
|
|
550
559
|
function responseType(ctx, methodDesc) {
|
|
551
|
-
return messageToTypeName(ctx, methodDesc.outputType);
|
|
560
|
+
return messageToTypeName(ctx, methodDesc.outputType, { keepValueType: true });
|
|
552
561
|
}
|
|
553
562
|
exports.responseType = responseType;
|
|
554
563
|
function responsePromise(ctx, methodDesc) {
|
package/build/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.protoFilesToGenerate = void 0;
|
|
3
|
+
exports.determineFieldJsonName = exports.FormattedMethodDescriptor = exports.assertInstanceOf = exports.maybePrefixPackage = exports.prefixDisableLinter = exports.maybeAddComment = exports.upperFirst = exports.lowerFirst = exports.singular = exports.fail = exports.readToBuffer = exports.protoFilesToGenerate = void 0;
|
|
4
4
|
const ts_poet_1 = require("ts-poet");
|
|
5
5
|
const options_1 = require("./options");
|
|
6
6
|
const case_1 = require("./case");
|
|
@@ -144,3 +144,12 @@ class FormattedMethodDescriptor {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
exports.FormattedMethodDescriptor = FormattedMethodDescriptor;
|
|
147
|
+
function determineFieldJsonName(field, options) {
|
|
148
|
+
// By default jsonName is camelCased by the protocol compilier unless the user has
|
|
149
|
+
// set a "json_name" option on this field.
|
|
150
|
+
if (field.jsonName.length > 0) {
|
|
151
|
+
return field.jsonName;
|
|
152
|
+
}
|
|
153
|
+
return case_1.maybeSnakeToCamel(field.name, options);
|
|
154
|
+
}
|
|
155
|
+
exports.determineFieldJsonName = determineFieldJsonName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-proto",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.92.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/plugin.js",
|
|
6
6
|
"repository": "github:stephenh/ts-proto",
|
|
@@ -9,10 +9,19 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "yarn tsc",
|
|
12
|
-
"
|
|
12
|
+
"build:test": "yarn proto2bin && yarn proto2pbjs && yarn bin2ts",
|
|
13
|
+
"build:test:local": "yarn proto2bin:local && yarn proto2pbjs:local && yarn bin2ts:local",
|
|
14
|
+
"proto2bin": "docker-compose run --rm protoc update-bins.sh",
|
|
15
|
+
"proto2bin-node": "docker-compose run --rm node update-bins.sh",
|
|
16
|
+
"proto2pbjs": "docker-compose run --rm protoc pbjs.sh",
|
|
17
|
+
"bin2ts": "docker-compose run --rm protoc codegen.sh",
|
|
18
|
+
"proto2bin:local": "integration/update-bins.sh",
|
|
19
|
+
"proto2pbjs:local": "integration/pbjs.sh",
|
|
20
|
+
"bin2ts:local": "integration/codegen.sh",
|
|
13
21
|
"test": "yarn jest -c jest.config.js --maxWorkers=2",
|
|
14
22
|
"prettier": "prettier --write {src,tests}/**/*.ts",
|
|
15
|
-
"prettier:check": "prettier --list-different {src,tests}/**/*.ts"
|
|
23
|
+
"prettier:check": "prettier --list-different {src,tests}/**/*.ts",
|
|
24
|
+
"setup:docker": "docker-compose build"
|
|
16
25
|
},
|
|
17
26
|
"files": [
|
|
18
27
|
"build"
|