Skip to content

Commit

Permalink
Add support for generating grpc-node service types (#193)
Browse files Browse the repository at this point in the history
* Run protoc twice, with and without service=true

* Use service=grpc-web instead of service=true

* Extract common code from service/grpcweb.ts

* Add support for service=grpc-node option

* Update yarn.lock

* Add additional grpcnode.ts tests

* Tests ready for review

* Warn when using deprecated service=true parameter
  • Loading branch information
esilkensen authored and jonny-improbable committed Aug 29, 2019
1 parent 7a65c0d commit 005b479
Show file tree
Hide file tree
Showing 165 changed files with 27,794 additions and 396 deletions.
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
This repository contains a [protoc](https://github.com/google/protobuf) plugin that generates TypeScript declarations
(`.d.ts` files) that match the JavaScript output of `protoc --js_out=import_style=commonjs,binary`. This plugin can
also output service definitions as both `.js` and `.d.ts` files in the structure required by [grpc-web](https://github.com/improbable-eng/grpc-web).
also output service definitions as both `.js` and `.d.ts` files in the structure required by [grpc-web](https://github.com/improbable-eng/grpc-web), and as `.d.ts` files in the structure required by [grpc-node](https://github.com/grpc/grpc-node).

This plugin is tested and written using TypeScript 2.7.

Expand Down Expand Up @@ -168,7 +168,7 @@ msg.setName("John Doe");

[grpc-web](https://github.com/improbable-eng/grpc-web) is a comparability layer on both the server and client-side which allows gRPC to function natively in modern web-browsers.

To generate client-side service stubs from your protobuf files you must configure ts-protoc-gen to emit service definitions by passing the `service=true` param to the `--ts_out` flag, eg:
To generate client-side service stubs from your protobuf files you must configure ts-protoc-gen to emit service definitions by passing the `service=grpc-web` param to the `--ts_out` flag, eg:

```
# Path to this plugin, Note this must be an abolsute path on Windows (see #15)
Expand All @@ -180,7 +180,7 @@ OUT_DIR="./generated"
protoc \
--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
--js_out="import_style=commonjs,binary:${OUT_DIR}" \
--ts_out="service=true:${OUT_DIR}" \
--ts_out="service=grpc-web:${OUT_DIR}" \
users.proto base.proto
```

Expand All @@ -203,6 +203,36 @@ client.getUser(req, (err, user) => {
});
```

### Generating gRPC Service Stubs for use with grpc-node

This plugin can generate `.d.ts` files for gRPC service definitions as required by [grpc-node](https://github.com/grpc/grpc-node).

To generate these declaration files from your protobuf files you must configure ts-protoc-gen to emit service definitions by passing the `service=grpc-node` param to the `--ts_out` flag, eg:

```
# Path to this plugin, Note this must be an abolsute path on Windows (see #15)
PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts"
# Path to the grpc_node_plugin
PROTOC_GEN_GRPC_PATH="./node_modules/.bin/grpc_tools_node_protoc_plugin"
# Directory to write generated code to (.js and .d.ts files)
OUT_DIR="./generated"
protoc \
--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
--plugin=protoc-gen-grpc=${PROTOC_GEN_GRPC_PATH} \
--js_out="import_style=commonjs,binary:${OUT_DIR}" \
--ts_out="service=grpc-node:${OUT_DIR}" \
--grpc_out="${OUT_DIR}" \
users.proto base.proto
```

The `generated` folder will now contain both `_grpc_pb.js` and `_grpc_pb.d.ts` files which you can reference in your TypeScript project to make RPCs.

**Note** This plugin does not generate the `_grpc_pb.js` files itself; those are generated by the protoc-gen-grpc plugin. This plugin only generates the `_grpc_pb.d.ts` files.


## Examples

- [Example output](https://github.com/improbable-eng/ts-protoc-gen/tree/master/examples) -- Code generated by `ts-protoc-gen`.
Expand Down
2 changes: 1 addition & 1 deletion defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _build_protoc_command(target, ctx):
protoc_command += " --plugin=protoc-gen-ts=%s" % (ctx.files._ts_protoc_gen[1].path)

protoc_output_dir = ctx.var["BINDIR"]
protoc_command += " --ts_out=service=true:%s" % (protoc_output_dir)
protoc_command += " --ts_out=service=grpc-web:%s" % (protoc_output_dir)
protoc_command += " --js_out=import_style=commonjs,binary:%s" % (protoc_output_dir)

descriptor_sets_paths = [desc.path for desc in target.proto.transitive_descriptor_sets.to_list()]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO
41 changes: 41 additions & 0 deletions examples/generated-grpc-node/proto/examplecom/annotations_pb.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// package: examplecom
// file: proto/examplecom/annotations.proto

import * as jspb from "google-protobuf";

export class AnnotatedMessage extends jspb.Message {
getMyunit64(): string;
setMyunit64(value: string): void;

getMyint64(): string;
setMyint64(value: string): void;

getMyfixed64(): string;
setMyfixed64(value: string): void;

getMysint64(): string;
setMysint64(value: string): void;

getMysfixed64(): string;
setMysfixed64(value: string): void;

serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): AnnotatedMessage.AsObject;
static toObject(includeInstance: boolean, msg: AnnotatedMessage): AnnotatedMessage.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: AnnotatedMessage, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): AnnotatedMessage;
static deserializeBinaryFromReader(message: AnnotatedMessage, reader: jspb.BinaryReader): AnnotatedMessage;
}

export namespace AnnotatedMessage {
export type AsObject = {
myunit64: string,
myint64: string,
myfixed64: string,
mysint64: string,
mysfixed64: string,
}
}

265 changes: 265 additions & 0 deletions examples/generated-grpc-node/proto/examplecom/annotations_pb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/**
* @fileoverview
* @enhanceable
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!

var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();

goog.exportSymbol('proto.examplecom.AnnotatedMessage', null, global);

/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.examplecom.AnnotatedMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.examplecom.AnnotatedMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.examplecom.AnnotatedMessage.displayName = 'proto.examplecom.AnnotatedMessage';
}


if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.examplecom.AnnotatedMessage.prototype.toObject = function(opt_includeInstance) {
return proto.examplecom.AnnotatedMessage.toObject(opt_includeInstance, this);
};


/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.examplecom.AnnotatedMessage} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.AnnotatedMessage.toObject = function(includeInstance, msg) {
var f, obj = {
myunit64: jspb.Message.getFieldWithDefault(msg, 1, "0"),
myint64: jspb.Message.getFieldWithDefault(msg, 2, "0"),
myfixed64: jspb.Message.getFieldWithDefault(msg, 3, "0"),
mysint64: jspb.Message.getFieldWithDefault(msg, 4, "0"),
mysfixed64: jspb.Message.getFieldWithDefault(msg, 5, "0")
};

if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}


/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.examplecom.AnnotatedMessage}
*/
proto.examplecom.AnnotatedMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.examplecom.AnnotatedMessage;
return proto.examplecom.AnnotatedMessage.deserializeBinaryFromReader(msg, reader);
};


/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.examplecom.AnnotatedMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.examplecom.AnnotatedMessage}
*/
proto.examplecom.AnnotatedMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readUint64String());
msg.setMyunit64(value);
break;
case 2:
var value = /** @type {string} */ (reader.readInt64String());
msg.setMyint64(value);
break;
case 3:
var value = /** @type {string} */ (reader.readFixed64String());
msg.setMyfixed64(value);
break;
case 4:
var value = /** @type {string} */ (reader.readSint64String());
msg.setMysint64(value);
break;
case 5:
var value = /** @type {string} */ (reader.readSfixed64String());
msg.setMysfixed64(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};


/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.examplecom.AnnotatedMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.examplecom.AnnotatedMessage.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};


/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.examplecom.AnnotatedMessage} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.AnnotatedMessage.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getMyunit64();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
1,
f
);
}
f = message.getMyint64();
if (parseInt(f, 10) !== 0) {
writer.writeInt64String(
2,
f
);
}
f = message.getMyfixed64();
if (parseInt(f, 10) !== 0) {
writer.writeFixed64String(
3,
f
);
}
f = message.getMysint64();
if (parseInt(f, 10) !== 0) {
writer.writeSint64String(
4,
f
);
}
f = message.getMysfixed64();
if (parseInt(f, 10) !== 0) {
writer.writeSfixed64String(
5,
f
);
}
};


/**
* optional uint64 myUnit64 = 1;
* @return {string}
*/
proto.examplecom.AnnotatedMessage.prototype.getMyunit64 = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "0"));
};


/** @param {string} value */
proto.examplecom.AnnotatedMessage.prototype.setMyunit64 = function(value) {
jspb.Message.setProto3StringIntField(this, 1, value);
};


/**
* optional int64 myInt64 = 2;
* @return {string}
*/
proto.examplecom.AnnotatedMessage.prototype.getMyint64 = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0"));
};


/** @param {string} value */
proto.examplecom.AnnotatedMessage.prototype.setMyint64 = function(value) {
jspb.Message.setProto3StringIntField(this, 2, value);
};


/**
* optional fixed64 myFixed64 = 3;
* @return {string}
*/
proto.examplecom.AnnotatedMessage.prototype.getMyfixed64 = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "0"));
};


/** @param {string} value */
proto.examplecom.AnnotatedMessage.prototype.setMyfixed64 = function(value) {
jspb.Message.setProto3StringIntField(this, 3, value);
};


/**
* optional sint64 mySint64 = 4;
* @return {string}
*/
proto.examplecom.AnnotatedMessage.prototype.getMysint64 = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "0"));
};


/** @param {string} value */
proto.examplecom.AnnotatedMessage.prototype.setMysint64 = function(value) {
jspb.Message.setProto3StringIntField(this, 4, value);
};


/**
* optional sfixed64 mySfixed64 = 5;
* @return {string}
*/
proto.examplecom.AnnotatedMessage.prototype.getMysfixed64 = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "0"));
};


/** @param {string} value */
proto.examplecom.AnnotatedMessage.prototype.setMysfixed64 = function(value) {
jspb.Message.setProto3StringIntField(this, 5, value);
};


goog.object.extend(exports, proto.examplecom);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO
Loading

0 comments on commit 005b479

Please sign in to comment.