ts-proto 1.118.0 → 1.121.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,24 @@
1
+ # [1.121.0](https://github.com/stephenh/ts-proto/compare/v1.120.0...v1.121.0) (2022-07-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add use-numeric-enum-json option. ([#625](https://github.com/stephenh/ts-proto/issues/625)) ([cd53d8c](https://github.com/stephenh/ts-proto/commit/cd53d8cacd6b4b8fa6517242020b216dd18eebdf))
7
+
8
+ # [1.120.0](https://github.com/stephenh/ts-proto/compare/v1.119.0...v1.120.0) (2022-07-21)
9
+
10
+
11
+ ### Features
12
+
13
+ * add meta-data to stream error ([#620](https://github.com/stephenh/ts-proto/issues/620)) ([b68f301](https://github.com/stephenh/ts-proto/commit/b68f301b5fb4222a3fce676a7e5036cf00e77e11))
14
+
15
+ # [1.119.0](https://github.com/stephenh/ts-proto/compare/v1.118.0...v1.119.0) (2022-07-21)
16
+
17
+
18
+ ### Features
19
+
20
+ * support grpc-web client straming ([#617](https://github.com/stephenh/ts-proto/issues/617)) ([d3e7f1f](https://github.com/stephenh/ts-proto/commit/d3e7f1f4aac12db87c54d6357e557a351c96a2ca))
21
+
1
22
  # [1.118.0](https://github.com/stephenh/ts-proto/compare/v1.117.1...v1.118.0) (2022-07-19)
2
23
 
3
24
 
package/README.markdown CHANGED
@@ -99,6 +99,13 @@ plugins:
99
99
  path: ../node_modules/ts-proto/protoc-gen-ts_proto
100
100
  ```
101
101
 
102
+ To prevent `buf push` from reading irrelevent `.proto` files, configure `buf.yaml` like so:
103
+
104
+ ```yaml
105
+ build:
106
+ excludes: [node_modules]
107
+ ```
108
+
102
109
  You can also use the official plugin published to the Buf Registry.
103
110
 
104
111
  ```yaml
@@ -428,6 +435,8 @@ Generated code will be placed in the Gradle build directory.
428
435
  Requires `onlyTypes=true`. Implies `useDate=string` and `stringEnums=true`. This option is to generate types that can be directly used with marshalling/unmarshalling Protobuf messages serialized as JSON.
429
436
  You may also want to set `useOptionals=all`, as gRPC gateways are not required to send default value for scalar values.
430
437
 
438
+ - With '--ts_proto_opt=useNumericEnumForJson=true`, the JSON converter (`toJSON`) will encode enum values as int, rather than a string literal.
439
+
431
440
  ### NestJS Support
432
441
 
433
442
  We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown).
package/build/enums.js CHANGED
@@ -91,17 +91,31 @@ function generateEnumToJson(ctx, fullName, enumDesc) {
91
91
  const { options, utils } = ctx;
92
92
  const chunks = [];
93
93
  const functionName = (0, case_1.camelCase)(fullName) + 'ToJSON';
94
- chunks.push((0, ts_poet_1.code) `export function ${(0, ts_poet_1.def)(functionName)}(object: ${fullName}): string {`);
94
+ chunks.push((0, ts_poet_1.code) `export function ${(0, ts_poet_1.def)(functionName)}(object: ${fullName}): ${ctx.options.useNumericEnumForJson ? 'number' : 'string'} {`);
95
95
  chunks.push((0, ts_poet_1.code) `switch (object) {`);
96
96
  for (const valueDesc of enumDesc.value) {
97
- chunks.push((0, ts_poet_1.code) `case ${fullName}.${valueDesc.name}: return "${valueDesc.name}";`);
97
+ if (ctx.options.useNumericEnumForJson) {
98
+ chunks.push((0, ts_poet_1.code) `case ${fullName}.${valueDesc.name}: return ${valueDesc.number};`);
99
+ }
100
+ else {
101
+ chunks.push((0, ts_poet_1.code) `case ${fullName}.${valueDesc.name}: return "${valueDesc.name}";`);
102
+ }
98
103
  }
99
104
  if (options.unrecognizedEnum) {
100
105
  chunks.push((0, ts_poet_1.code) `
101
- case ${fullName}.${UNRECOGNIZED_ENUM_NAME}:
106
+ case ${fullName}.${UNRECOGNIZED_ENUM_NAME}:`);
107
+ if (ctx.options.useNumericEnumForJson) {
108
+ chunks.push((0, ts_poet_1.code) `
109
+ default:
110
+ return ${UNRECOGNIZED_ENUM_VALUE};
111
+ `);
112
+ }
113
+ else {
114
+ chunks.push((0, ts_poet_1.code) `
102
115
  default:
103
116
  return "${UNRECOGNIZED_ENUM_NAME}";
104
117
  `);
118
+ }
105
119
  }
106
120
  else {
107
121
  // We use globalThis to avoid conflicts on protobuf types named `Error`.
@@ -18,7 +18,7 @@ function generateGrpcClientImpl(ctx, _fileDesc, serviceDesc) {
18
18
  // Create the constructor(rpc: Rpc)
19
19
  chunks.push((0, ts_poet_1.code) `
20
20
  private readonly rpc: Rpc;
21
-
21
+
22
22
  constructor(rpc: Rpc) {
23
23
  `);
24
24
  chunks.push((0, ts_poet_1.code) `this.rpc = rpc;`);
@@ -46,9 +46,11 @@ function generateRpcMethod(ctx, serviceDesc, methodDesc) {
46
46
  return (0, ts_poet_1.code) `
47
47
  ${methodDesc.formattedName}(
48
48
  request: ${inputType},
49
- metadata?: grpc.Metadata,
50
- ): ${returns} {
51
- throw new Error('ts-proto does not yet support client streaming!');
49
+ options?: {
50
+ metadata?: grpc.Metadata,
51
+ rpcOptions?: grpc.RpcOptions,
52
+ }): ${returns} {
53
+ return this.rpc.stream(${methodDescName(serviceDesc, methodDesc)}, request, options?.metadata, options?.rpcOptions)
52
54
  }
53
55
  `;
54
56
  }
@@ -83,7 +85,7 @@ exports.generateGrpcServiceDesc = generateGrpcServiceDesc;
83
85
  * implementation.
84
86
  */
85
87
  function generateGrpcMethodDesc(ctx, serviceDesc, methodDesc) {
86
- const inputType = (0, types_1.requestType)(ctx, methodDesc);
88
+ const inputType = (0, types_1.rawRequestType)(ctx, methodDesc);
87
89
  const outputType = (0, types_1.responseType)(ctx, methodDesc);
88
90
  // grpc-web expects this to be a class, but the ts-proto messages are just interfaces.
89
91
  //
@@ -109,13 +111,15 @@ function generateGrpcMethodDesc(ctx, serviceDesc, methodDesc) {
109
111
  deserializeBinary(data: Uint8Array) {
110
112
  return { ...${outputType}.decode(data), toObject() { return this; } };
111
113
  }
112
- }`;
114
+ }`;
115
+ const streaming = methodDesc.clientStreaming || methodDesc.serverStreaming;
116
+ const methodDef = streaming ? 'MethodDefinitionish' : 'UnaryMethodDefinitionish';
113
117
  return (0, ts_poet_1.code) `
114
- export const ${methodDescName(serviceDesc, methodDesc)}: UnaryMethodDefinitionish = {
118
+ export const ${methodDescName(serviceDesc, methodDesc)}: ${methodDef} = {
115
119
  methodName: "${methodDesc.name}",
116
120
  service: ${serviceDesc.name}Desc,
117
- requestStream: false,
118
- responseStream: ${methodDesc.serverStreaming ? 'true' : 'false'},
121
+ requestStream: ${methodDesc.clientStreaming ? true : false},
122
+ responseStream: ${methodDesc.serverStreaming ? true : false},
119
123
  requestType: ${requestFn} as any,
120
124
  responseType: ${responseFn} as any,
121
125
  };
@@ -133,6 +137,10 @@ function addGrpcWebMisc(ctx, hasStreamingMethods) {
133
137
  interface UnaryMethodDefinitionishR extends ${grpc}.UnaryMethodDefinition<any, any> { requestStream: any; responseStream: any; }
134
138
  `);
135
139
  chunks.push((0, ts_poet_1.code) `type UnaryMethodDefinitionish = UnaryMethodDefinitionishR;`);
140
+ chunks.push((0, ts_poet_1.code) `
141
+ interface MethodDefinitionishR extends ${grpc}.MethodDefinition<any, any> { requestStream: any; responseStream: any; }
142
+ `);
143
+ chunks.push((0, ts_poet_1.code) `type MethodDefinitionish = MethodDefinitionishR;`);
136
144
  chunks.push(generateGrpcWebRpcType(ctx, options.returnObservable, hasStreamingMethods));
137
145
  chunks.push(generateGrpcWebImpl(ctx, options.returnObservable, hasStreamingMethods));
138
146
  return (0, ts_poet_1.joinCode)(chunks, { on: '\n\n' });
@@ -156,8 +164,14 @@ function generateGrpcWebRpcType(ctx, returnObservable, hasStreamingMethods) {
156
164
  methodDesc: T,
157
165
  request: any,
158
166
  metadata: grpc.Metadata | undefined,
159
- ): ${(0, types_1.observableType)(ctx)}<any>;
160
- `);
167
+ ): ${(0, types_1.observableType)(ctx)}<any>;`);
168
+ chunks.push((0, ts_poet_1.code) `
169
+ stream<T extends MethodDefinitionish>(
170
+ methodDesc: T,
171
+ request: ${(0, types_1.observableType)(ctx)}<any>,
172
+ metadata: grpc.Metadata | undefined,
173
+ rpcOptions: grpc.RpcOptions | undefined
174
+ ): ${(0, types_1.observableType)(ctx)}<any>;`);
161
175
  }
162
176
  chunks.push((0, ts_poet_1.code) `}`);
163
177
  return (0, ts_poet_1.joinCode)(chunks, { on: '\n' });
@@ -178,7 +192,7 @@ function generateGrpcWebImpl(ctx, returnObservable, hasStreamingMethods) {
178
192
  export class GrpcWebImpl {
179
193
  private host: string;
180
194
  private options: ${options};
181
-
195
+
182
196
  constructor(host: string, options: ${options}) {
183
197
  this.host = host;
184
198
  this.options = options;
@@ -192,6 +206,7 @@ function generateGrpcWebImpl(ctx, returnObservable, hasStreamingMethods) {
192
206
  }
193
207
  if (hasStreamingMethods) {
194
208
  chunks.push(createInvokeMethod(ctx));
209
+ chunks.push(createStreamMethod(ctx));
195
210
  }
196
211
  chunks.push((0, ts_poet_1.code) `}`);
197
212
  return (0, ts_poet_1.joinCode)(chunks, { trim: false });
@@ -259,7 +274,7 @@ function createObservableUnaryMethod(ctx) {
259
274
  },
260
275
  });
261
276
  }).pipe(${take}(1));
262
- }
277
+ }
263
278
  `;
264
279
  }
265
280
  function createInvokeMethod(ctx) {
@@ -285,13 +300,16 @@ function createInvokeMethod(ctx) {
285
300
  metadata: maybeCombinedMetadata,
286
301
  debug: this.options.debug,
287
302
  onMessage: (next) => observer.next(next),
288
- onEnd: (code: ${grpc}.Code, message: string) => {
303
+ onEnd: (code: ${grpc}.Code, message: string, trailers: ${grpc}.Metadata) => {
289
304
  if (code === 0) {
290
305
  observer.complete();
291
306
  } else if (upStreamCodes.includes(code)) {
292
307
  setTimeout(upStream, DEFAULT_TIMEOUT_TIME);
293
308
  } else {
294
- observer.error(new Error(\`Error \${code} \${message}\`));
309
+ const err = new Error(message) as any;
310
+ err.code = code;
311
+ err.metadata = trailers;
312
+ observer.error(err);
295
313
  }
296
314
  },
297
315
  });
@@ -302,3 +320,50 @@ function createInvokeMethod(ctx) {
302
320
  }
303
321
  `;
304
322
  }
323
+ function createStreamMethod(ctx) {
324
+ return (0, ts_poet_1.code) `
325
+ stream<T extends MethodDefinitionish>(
326
+ methodDesc: T,
327
+ _request: ${(0, types_1.observableType)(ctx)}<any>,
328
+ metadata: ${grpc}.Metadata | undefined,
329
+ rpcOptions: ${grpc}.RpcOptions | undefined
330
+ ): ${(0, types_1.observableType)(ctx)}<any> {
331
+ const defaultOptions = {
332
+ host: this.host,
333
+ debug: rpcOptions?.debug || this.options.debug,
334
+ transport: rpcOptions?.transport || this.options.streamingTransport || this.options.transport,
335
+ }
336
+
337
+ let started = false;
338
+ const client = ${grpc}.client(methodDesc, defaultOptions);
339
+
340
+ const subscription = _request.subscribe((_req: any) => {
341
+ const request = { ..._req, ...methodDesc.requestType };
342
+ if (!started) {
343
+ client.start(metadata);
344
+ started = true;
345
+ }
346
+ client.send(request);
347
+ })
348
+
349
+ subscription.add(() => {
350
+ client.finishSend();
351
+ })
352
+
353
+ return new Observable((observer) => {
354
+ client.onEnd((code: ${grpc}.Code, message: string, trailers: ${grpc}.Metadata) => {
355
+ subscription.unsubscribe();
356
+ if (code === 0) {
357
+ observer.complete();
358
+ } else {
359
+ observer.error(new Error(\`Error \${code} \${message}\`));
360
+ }
361
+ })
362
+ client.onMessage((res: any) => {
363
+ observer.next(res);
364
+ })
365
+ observer.add(() => client.close())
366
+ }).pipe(share());
367
+ }
368
+ `;
369
+ }
@@ -42,7 +42,14 @@ function generateService(ctx, fileDesc, sourceInfo, serviceDesc) {
42
42
  // Use metadata as last argument for interface only configuration
43
43
  if (options.outputClientImpl === 'grpc-web') {
44
44
  // We have to use grpc.Metadata where grpc will come from @improbable-eng
45
- params.push((0, ts_poet_1.code) `metadata?: grpc.Metadata`);
45
+ if (methodDesc.clientStreaming) {
46
+ params.push((0, ts_poet_1.code) `options?: {
47
+ metadata?: grpc.Metadata,
48
+ rpcOptions?: grpc.RpcOptions}`);
49
+ }
50
+ else {
51
+ params.push((0, ts_poet_1.code) `metadata?: grpc.Metadata`);
52
+ }
46
53
  }
47
54
  else if (options.addGrpcMetadata) {
48
55
  const Metadata = (0, ts_poet_1.imp)('Metadata@@grpc/grpc-js');
package/build/main.js CHANGED
@@ -115,7 +115,6 @@ function generateFile(ctx, fileDesc) {
115
115
  }
116
116
  }, options);
117
117
  }
118
- let hasServerStreamingMethods = false;
119
118
  let hasStreamingMethods = false;
120
119
  (0, visit_1.visitServices)(fileDesc, sourceInfo, (serviceDesc, sInfo) => {
121
120
  if (options.nestJs) {
@@ -155,12 +154,7 @@ function generateFile(ctx, fileDesc) {
155
154
  chunks.push((0, generate_grpc_web_1.generateGrpcClientImpl)(ctx, fileDesc, serviceDesc));
156
155
  chunks.push((0, generate_grpc_web_1.generateGrpcServiceDesc)(fileDesc, serviceDesc));
157
156
  serviceDesc.method.forEach((method) => {
158
- if (!method.clientStreaming) {
159
- chunks.push((0, generate_grpc_web_1.generateGrpcMethodDesc)(ctx, serviceDesc, method));
160
- }
161
- if (method.serverStreaming) {
162
- hasServerStreamingMethods = true;
163
- }
157
+ chunks.push((0, generate_grpc_web_1.generateGrpcMethodDesc)(ctx, serviceDesc, method));
164
158
  });
165
159
  }
166
160
  }
@@ -179,7 +173,7 @@ function generateFile(ctx, fileDesc) {
179
173
  chunks.push((0, generate_services_1.generateRpcType)(ctx, hasStreamingMethods));
180
174
  }
181
175
  else if (options.outputClientImpl === 'grpc-web') {
182
- chunks.push((0, generate_grpc_web_1.addGrpcWebMisc)(ctx, hasServerStreamingMethods));
176
+ chunks.push((0, generate_grpc_web_1.addGrpcWebMisc)(ctx, hasStreamingMethods));
183
177
  }
184
178
  }
185
179
  if (options.context) {
@@ -1487,7 +1481,11 @@ function generateWrap(ctx, fullProtoTypeName, fieldNames) {
1487
1481
  }
1488
1482
  if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
1489
1483
  chunks.push((0, ts_poet_1.code) `wrap(paths: string[]): FieldMask {
1490
- return {paths: paths};
1484
+ const result = createBaseFieldMask();
1485
+
1486
+ result.paths = paths;
1487
+
1488
+ return result;
1491
1489
  }`);
1492
1490
  }
1493
1491
  return chunks;
package/build/options.js CHANGED
@@ -70,6 +70,7 @@ function defaultOptions() {
70
70
  unknownFields: false,
71
71
  usePrototypeForDefaults: false,
72
72
  useJsonWireFormat: false,
73
+ useNumericEnumForJson: false,
73
74
  };
74
75
  }
75
76
  exports.defaultOptions = defaultOptions;
package/build/types.js CHANGED
@@ -641,7 +641,7 @@ function responseObservable(ctx, methodDesc) {
641
641
  exports.responseObservable = responseObservable;
642
642
  function responsePromiseOrObservable(ctx, methodDesc) {
643
643
  const { options } = ctx;
644
- if (options.returnObservable || methodDesc.serverStreaming) {
644
+ if (options.returnObservable || methodDesc.serverStreaming || methodDesc.clientStreaming) {
645
645
  return responseObservable(ctx, methodDesc);
646
646
  }
647
647
  return responsePromise(ctx, methodDesc);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "1.118.0",
3
+ "version": "1.121.0",
4
4
  "description": "",
5
5
  "main": "build/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",