ts-proto 1.83.1 → 1.85.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.85.0](https://github.com/stephenh/ts-proto/compare/v1.84.0...v1.85.0) (2021-11-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * Streaming support ([#373](https://github.com/stephenh/ts-proto/issues/373)) ([459b94f](https://github.com/stephenh/ts-proto/commit/459b94f5b2988d58d186461332e888c3e511603a))
7
+
8
+ # [1.84.0](https://github.com/stephenh/ts-proto/compare/v1.83.3...v1.84.0) (2021-11-02)
9
+
10
+
11
+ ### Features
12
+
13
+ * Reduce code size by using nullish coalescing operator in fromPartial ([#376](https://github.com/stephenh/ts-proto/issues/376)) ([19d2ded](https://github.com/stephenh/ts-proto/commit/19d2deda2cf7c47b1b56bfc65cf58653291dba4a))
14
+
15
+ ## [1.83.3](https://github.com/stephenh/ts-proto/compare/v1.83.2...v1.83.3) (2021-10-28)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * fix codegen for maps with wrapper value type ([#370](https://github.com/stephenh/ts-proto/issues/370)) ([dd2481d](https://github.com/stephenh/ts-proto/commit/dd2481df0835faafc561aad4a4d0c9c2ff9d868a))
21
+
22
+ ## [1.83.2](https://github.com/stephenh/ts-proto/compare/v1.83.1...v1.83.2) (2021-10-26)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * Add missing defaults to fromPartial if options.oneof is UNIONS ([#375](https://github.com/stephenh/ts-proto/issues/375)) ([21781e9](https://github.com/stephenh/ts-proto/commit/21781e98bb6117b540be8c3f2c38ac3ad5cbbb44))
28
+
1
29
  ## [1.83.1](https://github.com/stephenh/ts-proto/compare/v1.83.0...v1.83.1) (2021-09-17)
2
30
 
3
31
 
@@ -43,9 +43,7 @@ function generateRpcMethod(ctx, serviceDesc, methodDesc) {
43
43
  const { options, utils } = ctx;
44
44
  const inputType = types_1.requestType(ctx, methodDesc);
45
45
  const partialInputType = ts_poet_1.code `${utils.DeepPartial}<${inputType}>`;
46
- const returns = options.returnObservable || methodDesc.serverStreaming
47
- ? types_1.responseObservable(ctx, methodDesc)
48
- : types_1.responsePromise(ctx, methodDesc);
46
+ const returns = types_1.responsePromiseOrObservable(ctx, methodDesc);
49
47
  const method = methodDesc.serverStreaming ? 'invoke' : 'unary';
50
48
  return ts_poet_1.code `
51
49
  ${methodDesc.formattedName}(
@@ -55,15 +55,7 @@ function generateService(ctx, fileDesc, sourceInfo, serviceDesc) {
55
55
  if (options.addNestjsRestParameter) {
56
56
  params.push(ts_poet_1.code `...rest: any`);
57
57
  }
58
- // Return observable for interface only configuration, passing returnObservable=true and methodDesc.serverStreaming=true
59
- let returnType;
60
- if (options.returnObservable || methodDesc.serverStreaming) {
61
- returnType = types_1.responseObservable(ctx, methodDesc);
62
- }
63
- else {
64
- returnType = types_1.responsePromise(ctx, methodDesc);
65
- }
66
- chunks.push(ts_poet_1.code `${methodDesc.formattedName}(${ts_poet_1.joinCode(params, { on: ',' })}): ${returnType};`);
58
+ chunks.push(ts_poet_1.code `${methodDesc.formattedName}(${ts_poet_1.joinCode(params, { on: ',' })}): ${types_1.responsePromiseOrObservable(ctx, methodDesc)};`);
67
59
  // If this is a batch method, auto-generate the singular version of it
68
60
  if (options.context) {
69
61
  const batchMethod = types_1.detectBatchMethod(ctx, fileDesc, serviceDesc, methodDesc);
@@ -83,22 +75,50 @@ function generateRegularRpcMethod(ctx, fileDesc, serviceDesc, methodDesc) {
83
75
  utils_1.assertInstanceOf(methodDesc, utils_1.FormattedMethodDescriptor);
84
76
  const { options } = ctx;
85
77
  const Reader = ts_poet_1.imp('Reader@protobufjs/minimal');
78
+ const rawInputType = types_1.rawRequestType(ctx, methodDesc);
86
79
  const inputType = types_1.requestType(ctx, methodDesc);
87
80
  const outputType = types_1.responseType(ctx, methodDesc);
88
81
  const params = [...(options.context ? [ts_poet_1.code `ctx: Context`] : []), ts_poet_1.code `request: ${inputType}`];
89
82
  const maybeCtx = options.context ? 'ctx,' : '';
83
+ let encode = ts_poet_1.code `${rawInputType}.encode(request).finish()`;
84
+ let decode = ts_poet_1.code `data => ${outputType}.decode(new ${Reader}(data))`;
85
+ if (methodDesc.clientStreaming) {
86
+ encode = ts_poet_1.code `request.pipe(${ts_poet_1.imp('map@rxjs/operators')}(request => ${encode}))`;
87
+ }
88
+ let returnVariable;
89
+ if (options.returnObservable || methodDesc.serverStreaming) {
90
+ returnVariable = 'result';
91
+ decode = ts_poet_1.code `result.pipe(${ts_poet_1.imp('map@rxjs/operators')}(${decode}))`;
92
+ }
93
+ else {
94
+ returnVariable = 'promise';
95
+ decode = ts_poet_1.code `promise.then(${decode})`;
96
+ }
97
+ let rpcMethod;
98
+ if (methodDesc.clientStreaming && methodDesc.serverStreaming) {
99
+ rpcMethod = 'bidirectionalStreamingRequest';
100
+ }
101
+ else if (methodDesc.serverStreaming) {
102
+ rpcMethod = 'serverStreamingRequest';
103
+ }
104
+ else if (methodDesc.clientStreaming) {
105
+ rpcMethod = 'clientStreamingRequest';
106
+ }
107
+ else {
108
+ rpcMethod = 'request';
109
+ }
90
110
  return ts_poet_1.code `
91
111
  ${methodDesc.formattedName}(
92
112
  ${ts_poet_1.joinCode(params, { on: ',' })}
93
- ): ${types_1.responsePromise(ctx, methodDesc)} {
94
- const data = ${inputType}.encode(request).finish();
95
- const promise = this.rpc.request(
113
+ ): ${types_1.responsePromiseOrObservable(ctx, methodDesc)} {
114
+ const data = ${encode};
115
+ const ${returnVariable} = this.rpc.${rpcMethod}(
96
116
  ${maybeCtx}
97
117
  "${utils_1.maybePrefixPackage(fileDesc, serviceDesc.name)}",
98
118
  "${methodDesc.name}",
99
119
  data
100
120
  );
101
- return promise.then(data => ${outputType}.decode(new ${Reader}(data)));
121
+ return ${decode};
102
122
  }
103
123
  `;
104
124
  }
@@ -217,24 +237,41 @@ function generateCachingRpcMethod(ctx, fileDesc, serviceDesc, methodDesc) {
217
237
  *
218
238
  * This lets clients pass in their own request-promise-ish client.
219
239
  *
240
+ * This also requires clientStreamingRequest, serverStreamingRequest and
241
+ * bidirectionalStreamingRequest methods if any of the RPCs is streaming.
242
+ *
220
243
  * We don't export this because if a project uses multiple `*.proto` files,
221
244
  * we don't want our the barrel imports in `index.ts` to have multiple `Rpc`
222
245
  * types.
223
246
  */
224
- function generateRpcType(ctx) {
247
+ function generateRpcType(ctx, hasStreamingMethods) {
225
248
  const { options } = ctx;
226
249
  const maybeContext = options.context ? '<Context>' : '';
227
250
  const maybeContextParam = options.context ? 'ctx: Context,' : '';
228
- return ts_poet_1.code `
229
- interface Rpc${maybeContext} {
230
- request(
251
+ const methods = [[ts_poet_1.code `request`, ts_poet_1.code `Uint8Array`, ts_poet_1.code `Promise<Uint8Array>`]];
252
+ if (hasStreamingMethods) {
253
+ const observable = ts_poet_1.imp('Observable@rxjs');
254
+ methods.push([ts_poet_1.code `clientStreamingRequest`, ts_poet_1.code `${observable}<Uint8Array>`, ts_poet_1.code `Promise<Uint8Array>`]);
255
+ methods.push([ts_poet_1.code `serverStreamingRequest`, ts_poet_1.code `Uint8Array`, ts_poet_1.code `${observable}<Uint8Array>`]);
256
+ methods.push([
257
+ ts_poet_1.code `bidirectionalStreamingRequest`,
258
+ ts_poet_1.code `${observable}<Uint8Array>`,
259
+ ts_poet_1.code `${observable}<Uint8Array>`,
260
+ ]);
261
+ }
262
+ const chunks = [];
263
+ chunks.push(ts_poet_1.code ` interface Rpc${maybeContext} {`);
264
+ methods.forEach((method) => {
265
+ chunks.push(ts_poet_1.code `
266
+ ${method[0]}(
231
267
  ${maybeContextParam}
232
268
  service: string,
233
269
  method: string,
234
- data: Uint8Array
235
- ): Promise<Uint8Array>;
236
- }
237
- `;
270
+ data: ${method[1]}
271
+ ): ${method[2]};`);
272
+ });
273
+ chunks.push(ts_poet_1.code ` }`);
274
+ return ts_poet_1.joinCode(chunks, { on: '\n' });
238
275
  }
239
276
  exports.generateRpcType = generateRpcType;
240
277
  function generateDataLoadersType() {
package/build/main.js CHANGED
@@ -91,6 +91,7 @@ function generateFile(ctx, fileDesc) {
91
91
  }
92
92
  }, options);
93
93
  }
94
+ let hasServerStreamingMethods = false;
94
95
  let hasStreamingMethods = false;
95
96
  visit_1.visitServices(fileDesc, sourceInfo, (serviceDesc, sInfo) => {
96
97
  if (options.nestJs) {
@@ -126,18 +127,23 @@ function generateFile(ctx, fileDesc) {
126
127
  serviceDesc.method.forEach((method) => {
127
128
  chunks.push(generate_grpc_web_1.generateGrpcMethodDesc(ctx, serviceDesc, method));
128
129
  if (method.serverStreaming) {
129
- hasStreamingMethods = true;
130
+ hasServerStreamingMethods = true;
130
131
  }
131
132
  });
132
133
  }
133
134
  }
135
+ serviceDesc.method.forEach((methodDesc, index) => {
136
+ if (methodDesc.serverStreaming || methodDesc.clientStreaming) {
137
+ hasStreamingMethods = true;
138
+ }
139
+ });
134
140
  });
135
141
  if (options.outputServices === options_1.ServiceOption.DEFAULT && options.outputClientImpl && fileDesc.service.length > 0) {
136
142
  if (options.outputClientImpl === true) {
137
- chunks.push(generate_services_1.generateRpcType(ctx));
143
+ chunks.push(generate_services_1.generateRpcType(ctx, hasStreamingMethods));
138
144
  }
139
145
  else if (options.outputClientImpl === 'grpc-web') {
140
- chunks.push(generate_grpc_web_1.addGrpcWebMisc(ctx, hasStreamingMethods));
146
+ chunks.push(generate_grpc_web_1.addGrpcWebMisc(ctx, hasServerStreamingMethods));
141
147
  }
142
148
  }
143
149
  if (options.context) {
@@ -632,10 +638,18 @@ function generateEncode(ctx, fullName, messageDesc) {
632
638
  }
633
639
  if (types_1.isRepeated(field)) {
634
640
  if (types_1.isMapType(ctx, messageDesc, field)) {
641
+ const valueType = typeMap.get(field.typeName)[2].field[1];
635
642
  const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : '';
643
+ const entryWriteSnippet = types_1.isValueType(ctx, valueType)
644
+ ? ts_poet_1.code `
645
+ if (value !== undefined) {
646
+ ${writeSnippet(`{ ${maybeTypeField} key: key as any, value }`)};
647
+ }
648
+ `
649
+ : writeSnippet(`{ ${maybeTypeField} key: key as any, value }`);
636
650
  chunks.push(ts_poet_1.code `
637
651
  Object.entries(message.${fieldName}).forEach(([key, value]) => {
638
- ${writeSnippet(`{ ${maybeTypeField} key: key as any, value }`)};
652
+ ${entryWriteSnippet}
639
653
  });
640
654
  `);
641
655
  }
@@ -814,6 +828,10 @@ function generateFromJson(ctx, fullName, messageDesc) {
814
828
  (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.TIMESTAMP)) {
815
829
  return ts_poet_1.code `${utils.fromJsonTimestamp}(${from})`;
816
830
  }
831
+ else if (types_1.isValueType(ctx, valueType)) {
832
+ const type = types_1.basicTypeName(ctx, valueType);
833
+ return ts_poet_1.code `${from} as ${type}`;
834
+ }
817
835
  else {
818
836
  const type = types_1.basicTypeName(ctx, valueType);
819
837
  return ts_poet_1.code `${type}.fromJSON(${from})`;
@@ -917,7 +935,7 @@ function generateToJson(ctx, fullName, messageDesc) {
917
935
  else if (types_1.isTimestamp(valueType) && options.useDate === options_1.DateOption.TIMESTAMP) {
918
936
  return ts_poet_1.code `${utils.fromTimestamp}(${from}).toISOString()`;
919
937
  }
920
- else if (types_1.isScalar(valueType)) {
938
+ else if (types_1.isScalar(valueType) || types_1.isValueType(ctx, valueType)) {
921
939
  return ts_poet_1.code `${from}`;
922
940
  }
923
941
  else {
@@ -990,12 +1008,6 @@ function generateFromPartial(ctx, fullName, messageDesc) {
990
1008
  fromPartial(${messageDesc.field.length > 0 ? 'object' : '_'}: ${utils.DeepPartial}<${fullName}>): ${fullName} {
991
1009
  const message = { ...base${fullName} } as ${fullName};
992
1010
  `);
993
- // initialize all lists
994
- messageDesc.field.filter(types_1.isRepeated).forEach((field) => {
995
- const value = types_1.isMapType(ctx, messageDesc, field) ? '{}' : '[]';
996
- const name = case_1.maybeSnakeToCamel(field.name, options);
997
- chunks.push(ts_poet_1.code `message.${name} = ${value};`);
998
- });
999
1011
  // add a check for each incoming field
1000
1012
  messageDesc.field.forEach((field) => {
1001
1013
  const fieldName = case_1.maybeSnakeToCamel(field.name, options);
@@ -1024,6 +1036,9 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1024
1036
  (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) {
1025
1037
  return ts_poet_1.code `${from}`;
1026
1038
  }
1039
+ else if (types_1.isValueType(ctx, valueType)) {
1040
+ return ts_poet_1.code `${from}`;
1041
+ }
1027
1042
  else {
1028
1043
  const type = types_1.basicTypeName(ctx, valueType);
1029
1044
  return ts_poet_1.code `${type}.fromPartial(${from})`;
@@ -1040,6 +1055,9 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1040
1055
  };
1041
1056
  // and then use the snippet to handle repeated fields if necessary
1042
1057
  if (types_1.isRepeated(field)) {
1058
+ const value = types_1.isMapType(ctx, messageDesc, field) ? '{}' : '[]';
1059
+ const name = case_1.maybeSnakeToCamel(field.name, options);
1060
+ chunks.push(ts_poet_1.code `message.${name} = ${value};`);
1043
1061
  chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1044
1062
  if (types_1.isMapType(ctx, messageDesc, field)) {
1045
1063
  const i = maybeCastToNumber(ctx, messageDesc, field, 'key');
@@ -1058,6 +1076,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1058
1076
  }
1059
1077
  `);
1060
1078
  }
1079
+ chunks.push(ts_poet_1.code `}`);
1061
1080
  }
1062
1081
  else if (types_1.isWithinOneOfThatShouldBeUnion(options, field)) {
1063
1082
  let oneofName = case_1.maybeSnakeToCamel(messageDesc.oneofDecl[field.oneofIndex].name, options);
@@ -1069,25 +1088,34 @@ function generateFromPartial(ctx, fullName, messageDesc) {
1069
1088
  && object.${oneofName}?.${fieldName} !== null
1070
1089
  ) {
1071
1090
  message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${v} };
1091
+ }
1072
1092
  `);
1073
1093
  }
1074
- else {
1094
+ else if ((types_1.isLong(field) || types_1.isLongValueType(field)) && options.forceLong === options_1.LongOption.LONG) {
1095
+ const v = readSnippet(`object.${fieldName}`);
1096
+ const type = types_1.basicTypeName(ctx, field);
1075
1097
  chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1076
- if ((types_1.isLong(field) || types_1.isLongValueType(field)) && options.forceLong === options_1.LongOption.LONG) {
1077
- const v = readSnippet(`object.${fieldName}`);
1078
- const type = types_1.basicTypeName(ctx, field);
1079
- chunks.push(ts_poet_1.code `message.${fieldName} = ${v} as ${type};`);
1080
- }
1081
- else {
1082
- chunks.push(ts_poet_1.code `message.${fieldName} = ${readSnippet(`object.${fieldName}`)};`);
1083
- }
1098
+ chunks.push(ts_poet_1.code `message.${fieldName} = ${v} as ${type};`);
1099
+ chunks.push(ts_poet_1.code `} else {`);
1100
+ const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1101
+ chunks.push(ts_poet_1.code `message.${fieldName} = ${fallback}`);
1102
+ chunks.push(ts_poet_1.code `}`);
1084
1103
  }
1085
- if (!types_1.isRepeated(field) && options.oneof !== options_1.OneofOption.UNIONS) {
1104
+ else if (types_1.isPrimitive(field) ||
1105
+ (types_1.isTimestamp(field) && (options.useDate === options_1.DateOption.DATE || options.useDate === options_1.DateOption.STRING)) ||
1106
+ types_1.isValueType(ctx, field)) {
1107
+ // An optimized case of the else below that works when `readSnippet` returns the plain input
1108
+ const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1109
+ chunks.push(ts_poet_1.code `message.${fieldName} = object.${fieldName} ?? ${fallback};`);
1110
+ }
1111
+ else {
1112
+ chunks.push(ts_poet_1.code `if (object.${fieldName} !== undefined && object.${fieldName} !== null) {`);
1113
+ chunks.push(ts_poet_1.code `message.${fieldName} = ${readSnippet(`object.${fieldName}`)};`);
1086
1114
  chunks.push(ts_poet_1.code `} else {`);
1087
- const v = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1088
- chunks.push(ts_poet_1.code `message.${fieldName} = ${v}`);
1115
+ const fallback = types_1.isWithinOneOf(field) ? 'undefined' : types_1.defaultValue(ctx, field);
1116
+ chunks.push(ts_poet_1.code `message.${fieldName} = ${fallback}`);
1117
+ chunks.push(ts_poet_1.code `}`);
1089
1118
  }
1090
- chunks.push(ts_poet_1.code `}`);
1091
1119
  });
1092
1120
  // and then wrap up the switch/while/return
1093
1121
  chunks.push(ts_poet_1.code `return message;`);
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.responseObservable = exports.responsePromise = exports.responseType = exports.requestType = 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.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;
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");
@@ -512,8 +512,12 @@ function detectMapType(ctx, messageDesc, fieldDesc) {
512
512
  return undefined;
513
513
  }
514
514
  exports.detectMapType = detectMapType;
515
+ function rawRequestType(ctx, methodDesc) {
516
+ return messageToTypeName(ctx, methodDesc.inputType);
517
+ }
518
+ exports.rawRequestType = rawRequestType;
515
519
  function requestType(ctx, methodDesc) {
516
- let typeName = messageToTypeName(ctx, methodDesc.inputType);
520
+ let typeName = rawRequestType(ctx, methodDesc);
517
521
  if (methodDesc.clientStreaming) {
518
522
  return ts_poet_1.code `${ts_poet_1.imp('Observable@rxjs')}<${typeName}>`;
519
523
  }
@@ -532,6 +536,14 @@ function responseObservable(ctx, methodDesc) {
532
536
  return ts_poet_1.code `${ts_poet_1.imp('Observable@rxjs')}<${responseType(ctx, methodDesc)}>`;
533
537
  }
534
538
  exports.responseObservable = responseObservable;
539
+ function responsePromiseOrObservable(ctx, methodDesc) {
540
+ const { options } = ctx;
541
+ if (options.returnObservable || methodDesc.serverStreaming) {
542
+ return responseObservable(ctx, methodDesc);
543
+ }
544
+ return responsePromise(ctx, methodDesc);
545
+ }
546
+ exports.responsePromiseOrObservable = responsePromiseOrObservable;
535
547
  function detectBatchMethod(ctx, fileDesc, serviceDesc, methodDesc) {
536
548
  const { typeMap } = ctx;
537
549
  const nameMatches = methodDesc.name.startsWith('Batch');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.83.1",
3
+ "version": "1.85.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",