From 24fbad6bb9b000066c2e77c0a4fae5973d200f48 Mon Sep 17 00:00:00 2001 From: Jonny Reeves Date: Sat, 1 Sep 2018 07:55:09 +0100 Subject: [PATCH 01/15] Add support for jstype annotation (#104) * Add support for jstype annotation Required an upgrade to google-protobuf 3.6.1, otherwise very straight forward. Fixes #17 * Add test coverage for all support field types --- README.md | 9 + .../proto/examplecom/annotations_pb.d.ts | 41 +++ .../proto/examplecom/annotations_pb.js | 265 ++++++++++++++++++ .../examplecom/annotations_pb_service.d.ts | 3 + .../examplecom/annotations_pb_service.js | 3 + package-lock.json | 6 +- package.json | 2 +- proto/examplecom/annotations.proto | 11 + src/ts/message.ts | 21 +- test/integration/annotations.ts | 24 ++ 10 files changed, 379 insertions(+), 6 deletions(-) create mode 100644 examples/generated/proto/examplecom/annotations_pb.d.ts create mode 100644 examples/generated/proto/examplecom/annotations_pb.js create mode 100644 examples/generated/proto/examplecom/annotations_pb_service.d.ts create mode 100644 examples/generated/proto/examplecom/annotations_pb_service.js create mode 100644 proto/examplecom/annotations.proto create mode 100644 test/integration/annotations.ts diff --git a/README.md b/README.md index ccec8767..b5ae07f0 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,12 @@ client.getUser(req, (err, user) => { /* ... */ }); For a sample of the generated protos and service definitions, see [examples](https://github.com/improbable-eng/ts-protoc-gen/tree/master/examples). To generate the examples from protos, please run `./generate.sh` + +## Gotchas +By default the google-protobuf library will use the JavaScript number type to store 64bit float and integer values; this can lead to overflow problems as you exceed JavaScript's `Number.MAX_VALUE`. To work around this, you should consider using the `jstype` annotation on any 64bit fields, ie: + +```proto +message Example { + uint64 bigInt = 1 [jstype = JS_STRING]; +} +``` \ No newline at end of file diff --git a/examples/generated/proto/examplecom/annotations_pb.d.ts b/examples/generated/proto/examplecom/annotations_pb.d.ts new file mode 100644 index 00000000..2c0f3e2d --- /dev/null +++ b/examples/generated/proto/examplecom/annotations_pb.d.ts @@ -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}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + 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, + } +} + diff --git a/examples/generated/proto/examplecom/annotations_pb.js b/examples/generated/proto/examplecom/annotations_pb.js new file mode 100644 index 00000000..c0698227 --- /dev/null +++ b/examples/generated/proto/examplecom/annotations_pb.js @@ -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_, 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); diff --git a/examples/generated/proto/examplecom/annotations_pb_service.d.ts b/examples/generated/proto/examplecom/annotations_pb_service.d.ts new file mode 100644 index 00000000..414651f0 --- /dev/null +++ b/examples/generated/proto/examplecom/annotations_pb_service.d.ts @@ -0,0 +1,3 @@ +// package: examplecom +// file: proto/examplecom/annotations.proto + diff --git a/examples/generated/proto/examplecom/annotations_pb_service.js b/examples/generated/proto/examplecom/annotations_pb_service.js new file mode 100644 index 00000000..414651f0 --- /dev/null +++ b/examples/generated/proto/examplecom/annotations_pb_service.js @@ -0,0 +1,3 @@ +// package: examplecom +// file: proto/examplecom/annotations.proto + diff --git a/package-lock.json b/package-lock.json index 0a646d85..3ab159c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -291,9 +291,9 @@ } }, "google-protobuf": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.5.0.tgz", - "integrity": "sha1-uMxjx02DRXvYqakEUDyO+ya8ozk=" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.6.1.tgz", + "integrity": "sha512-SJYemeX5GjDLPnadcmCNQePQHCS4Hl5fOcI/JawqDIYFhCmrtYAjcx/oTQx/Wi8UuCuZQhfvftbmPePPAYHFtA==" }, "growl": { "version": "1.10.5", diff --git a/package.json b/package.json index cf0b2ac6..15dc1ed1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "author": "Improbable", "license": "Apache-2.0", "dependencies": { - "google-protobuf": "^3.5.0" + "google-protobuf": "^3.6.1" }, "devDependencies": { "@types/chai": "^3.5.2", diff --git a/proto/examplecom/annotations.proto b/proto/examplecom/annotations.proto new file mode 100644 index 00000000..5cd6fbc7 --- /dev/null +++ b/proto/examplecom/annotations.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package examplecom; + +message AnnotatedMessage { + uint64 myUnit64 = 1 [jstype = JS_STRING]; + int64 myInt64 = 2 [jstype = JS_STRING]; + fixed64 myFixed64 = 3 [jstype = JS_STRING]; + sint64 mySint64 = 4 [jstype = JS_STRING]; + sfixed64 mySfixed64 = 5 [jstype = JS_STRING]; +} diff --git a/src/ts/message.ts b/src/ts/message.ts index a1485901..58639b1c 100644 --- a/src/ts/message.ts +++ b/src/ts/message.ts @@ -3,12 +3,16 @@ import { withinNamespaceFromExportEntry, normaliseFieldObjectName } from "../util"; import {ExportMap} from "../ExportMap"; -import {FieldDescriptorProto, FileDescriptorProto, DescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb"; +import { + FieldDescriptorProto, FileDescriptorProto, DescriptorProto, + FieldOptions +} from "google-protobuf/google/protobuf/descriptor_pb"; import {MESSAGE_TYPE, BYTES_TYPE, ENUM_TYPE, getFieldType, getTypeName} from "./FieldTypes"; import {Printer} from "../Printer"; import {printEnum} from "./enum"; import {printOneOfDecl} from "./oneof"; import {printExtension} from "./extensions"; +import JSType = FieldOptions.JSType; function hasFieldPresence(field: FieldDescriptorProto, fileDescriptor: FileDescriptorProto): boolean { if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) { @@ -104,7 +108,20 @@ export function printMessage(fileName: string, exportMap: ExportMap, messageDesc exportType = filePathToPseudoNamespace(fieldEnumType.fileName) + "." + withinNamespace; } } else { - exportType = getTypeName(type); + if (field.getOptions() && field.getOptions().hasJstype()) { + switch (field.getOptions().getJstype()) { + case JSType.JS_NUMBER: + exportType = "number"; + break; + case JSType.JS_STRING: + exportType = "string"; + break; + default: + exportType = getTypeName(type); + } + } else { + exportType = getTypeName(type); + } } let hasClearMethod = false; diff --git a/test/integration/annotations.ts b/test/integration/annotations.ts new file mode 100644 index 00000000..1061efac --- /dev/null +++ b/test/integration/annotations.ts @@ -0,0 +1,24 @@ +import { assert } from "chai"; +import { AnnotatedMessage } from "../../examples/generated/proto/examplecom/annotations_pb"; + +describe("annotations", () => { + it("should detect and honor the `jstype` annotation", () => { + const unsafeInteger = Number.MAX_VALUE.toString() + "9999"; + const msg = new AnnotatedMessage(); + + msg.setMyfixed64(unsafeInteger); + assert.strictEqual(msg.getMyfixed64(), unsafeInteger); + + msg.setMyunit64(unsafeInteger); + assert.strictEqual(msg.getMyunit64(), unsafeInteger); + + msg.setMyint64(unsafeInteger); + assert.strictEqual(msg.getMyint64(), unsafeInteger); + + msg.setMysfixed64(unsafeInteger); + assert.strictEqual(msg.getMysfixed64(), unsafeInteger); + + msg.setMysint64(unsafeInteger); + assert.strictEqual(msg.getMysint64(), unsafeInteger); + }); +}); From 595959942e1b39d548b57ceae82142af7f11169d Mon Sep 17 00:00:00 2001 From: John Reeves Date: Thu, 6 Sep 2018 13:11:06 +0100 Subject: [PATCH 02/15] Implement Client Streaming and BiDi Streaming for grpc-web (#82) * WIP * Fix tests * Improve test coverage * WIP * Finish off tests * Document connecting Chrome Inspector to test runs. * Reinstate test for service creation. * Reinstate mocha-reporter --- CONTRIBUTING.md | 9 +- README.md | 1 + .../proto/examplecom/simple_service_pb.d.ts | 1 + .../proto/examplecom/simple_service_pb.js | 1 + .../examplecom/simple_service_pb_service.d.ts | 44 +- .../examplecom/simple_service_pb_service.js | 113 ++++- .../generated/proto/orphan_pb_service.d.ts | 15 + package-lock.json | 6 +- package.json | 2 +- proto/examplecom/simple_service.proto | 7 +- src/service/grpcweb.ts | 117 +++++- test/helpers/fakeGrpcTransport.ts | 8 + test/integration/service/grpcweb.ts | 386 ++++++++++++++++-- test/mocha-run-suite.sh | 7 +- 14 files changed, 649 insertions(+), 68 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fccc378..e3e809e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,4 +15,11 @@ The following is a set of guidelines for contributing to `ts-protoc-gen`. Please review and follow our [Code of Conduct](https://github.com/improbable-eng/ts-protoc-gen/blob/master/README.md). ## Releasing -Your changes will be released with the next version release. \ No newline at end of file +Your changes will be released with the next version release. + +## Debugging +You can attach the Chrome Inspector when running the tests by setting the `MOCHA_DEBUG` environment variable before running the tests, ie: + +``` +MOCHA_DEBUG=true npm test +``` \ No newline at end of file diff --git a/README.md b/README.md index b5ae07f0..ad64ccfa 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ For our latest build straight from master: ```bash npm install ts-protoc-gen@next ``` + ### bazel Include the following in your `WORKSPACE`: ```python diff --git a/examples/generated/proto/examplecom/simple_service_pb.d.ts b/examples/generated/proto/examplecom/simple_service_pb.d.ts index f8c9f620..3f35e95b 100644 --- a/examples/generated/proto/examplecom/simple_service_pb.d.ts +++ b/examples/generated/proto/examplecom/simple_service_pb.d.ts @@ -3,6 +3,7 @@ import * as jspb from "google-protobuf"; import * as proto_othercom_external_child_message_pb from "../../proto/othercom/external_child_message_pb"; +import * as google_protobuf_empty_pb from "google-protobuf/google/protobuf/empty_pb"; import * as google_protobuf_timestamp_pb from "google-protobuf/google/protobuf/timestamp_pb"; export class UnaryRequest extends jspb.Message { diff --git a/examples/generated/proto/examplecom/simple_service_pb.js b/examples/generated/proto/examplecom/simple_service_pb.js index 1b4b5107..2674f47d 100644 --- a/examples/generated/proto/examplecom/simple_service_pb.js +++ b/examples/generated/proto/examplecom/simple_service_pb.js @@ -12,6 +12,7 @@ var goog = jspb; var global = Function('return this')(); var proto_othercom_external_child_message_pb = require('../../proto/othercom/external_child_message_pb.js'); +var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js'); var google_protobuf_timestamp_pb = require('google-protobuf/google/protobuf/timestamp_pb.js'); goog.exportSymbol('proto.examplecom.StreamRequest', null, global); goog.exportSymbol('proto.examplecom.UnaryRequest', null, global); diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts index b2a436ba..cf7f30c8 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts +++ b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts @@ -3,6 +3,7 @@ import * as proto_examplecom_simple_service_pb from "../../proto/examplecom/simple_service_pb"; import * as proto_othercom_external_child_message_pb from "../../proto/othercom/external_child_message_pb"; +import * as google_protobuf_empty_pb from "google-protobuf/google/protobuf/empty_pb"; import {grpc} from "grpc-web-client"; type SimpleServiceDoUnary = { @@ -14,7 +15,7 @@ type SimpleServiceDoUnary = { readonly responseType: typeof proto_othercom_external_child_message_pb.ExternalChildMessage; }; -type SimpleServiceDoStream = { +type SimpleServiceDoServerStream = { readonly methodName: string; readonly service: typeof SimpleService; readonly requestStream: false; @@ -23,6 +24,24 @@ type SimpleServiceDoStream = { readonly responseType: typeof proto_othercom_external_child_message_pb.ExternalChildMessage; }; +type SimpleServiceDoClientStream = { + readonly methodName: string; + readonly service: typeof SimpleService; + readonly requestStream: true; + readonly responseStream: false; + readonly requestType: typeof proto_examplecom_simple_service_pb.StreamRequest; + readonly responseType: typeof google_protobuf_empty_pb.Empty; +}; + +type SimpleServiceDoBidiStream = { + readonly methodName: string; + readonly service: typeof SimpleService; + readonly requestStream: true; + readonly responseStream: true; + readonly requestType: typeof proto_examplecom_simple_service_pb.StreamRequest; + readonly responseType: typeof proto_othercom_external_child_message_pb.ExternalChildMessage; +}; + type SimpleServiceDelete = { readonly methodName: string; readonly service: typeof SimpleService; @@ -35,7 +54,9 @@ type SimpleServiceDelete = { export class SimpleService { static readonly serviceName: string; static readonly DoUnary: SimpleServiceDoUnary; - static readonly DoStream: SimpleServiceDoStream; + static readonly DoServerStream: SimpleServiceDoServerStream; + static readonly DoClientStream: SimpleServiceDoClientStream; + static readonly DoBidiStream: SimpleServiceDoBidiStream; static readonly Delete: SimpleServiceDelete; } @@ -49,6 +70,21 @@ interface ResponseStream { on(type: 'end', handler: () => void): ResponseStream; on(type: 'status', handler: (status: Status) => void): ResponseStream; } +interface RequestStream { + write(message: T): RequestStream; + end(): void; + cancel(): void; + on(type: 'end', handler: () => void): RequestStream; + on(type: 'status', handler: (status: Status) => void): RequestStream; +} +interface BidirectionalStream { + write(message: T): BidirectionalStream; + end(): void; + cancel(): void; + on(type: 'data', handler: (message: T) => void): BidirectionalStream; + on(type: 'end', handler: () => void): BidirectionalStream; + on(type: 'status', handler: (status: Status) => void): BidirectionalStream; +} export class SimpleServiceClient { readonly serviceHost: string; @@ -63,7 +99,9 @@ export class SimpleServiceClient { requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, callback: (error: ServiceError, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void ): void; - doStream(requestMessage: proto_examplecom_simple_service_pb.StreamRequest, metadata?: grpc.Metadata): ResponseStream; + doServerStream(requestMessage: proto_examplecom_simple_service_pb.StreamRequest, metadata?: grpc.Metadata): ResponseStream; + doClientStream(metadata?: grpc.Metadata): RequestStream; + doBidiStream(metadata?: grpc.Metadata): BidirectionalStream; delete( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, metadata: grpc.Metadata, diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.js b/examples/generated/proto/examplecom/simple_service_pb_service.js index 7e5d456c..14a5a407 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.js +++ b/examples/generated/proto/examplecom/simple_service_pb_service.js @@ -3,6 +3,7 @@ var proto_examplecom_simple_service_pb = require("../../proto/examplecom/simple_service_pb"); var proto_othercom_external_child_message_pb = require("../../proto/othercom/external_child_message_pb"); +var google_protobuf_empty_pb = require("google-protobuf/google/protobuf/empty_pb"); var grpc = require("grpc-web-client").grpc; var SimpleService = (function () { @@ -20,8 +21,8 @@ SimpleService.DoUnary = { responseType: proto_othercom_external_child_message_pb.ExternalChildMessage }; -SimpleService.DoStream = { - methodName: "DoStream", +SimpleService.DoServerStream = { + methodName: "DoServerStream", service: SimpleService, requestStream: false, responseStream: true, @@ -29,6 +30,24 @@ SimpleService.DoStream = { responseType: proto_othercom_external_child_message_pb.ExternalChildMessage }; +SimpleService.DoClientStream = { + methodName: "DoClientStream", + service: SimpleService, + requestStream: true, + responseStream: false, + requestType: proto_examplecom_simple_service_pb.StreamRequest, + responseType: google_protobuf_empty_pb.Empty +}; + +SimpleService.DoBidiStream = { + methodName: "DoBidiStream", + service: SimpleService, + requestStream: true, + responseStream: true, + requestType: proto_examplecom_simple_service_pb.StreamRequest, + responseType: proto_othercom_external_child_message_pb.ExternalChildMessage +}; + SimpleService.Delete = { methodName: "Delete", service: SimpleService, @@ -67,13 +86,13 @@ SimpleServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat }); }; -SimpleServiceClient.prototype.doStream = function doStream(requestMessage, metadata) { +SimpleServiceClient.prototype.doServerStream = function doServerStream(requestMessage, metadata) { var listeners = { data: [], end: [], status: [] }; - var client = grpc.invoke(SimpleService.DoStream, { + var client = grpc.invoke(SimpleService.DoServerStream, { request: requestMessage, host: this.serviceHost, metadata: metadata, @@ -106,6 +125,92 @@ SimpleServiceClient.prototype.doStream = function doStream(requestMessage, metad }; }; +SimpleServiceClient.prototype.doClientStream = function doClientStream(metadata) { + var listeners = { + end: [], + status: [] + }; + var client = grpc.client(SimpleService.DoClientStream, { + host: this.serviceHost, + metadata: metadata, + transport: this.options.transport + }); + client.onEnd(function (status, statusMessage, trailers) { + listeners.end.forEach(function (handler) { + handler(); + }); + listeners.status.forEach(function (handler) { + handler({ code: status, details: statusMessage, metadata: trailers }); + }); + listeners = null; + }); + return { + on: function (type, handler) { + listeners[type].push(handler); + return this; + }, + write: function (requestMessage) { + if (!client.started) { + client.start(metadata); + } + client.send(requestMessage); + return this; + }, + end: function () { + client.finishSend(); + }, + cancel: function () { + listeners = null; + client.close(); + } + }; +}; + +SimpleServiceClient.prototype.doBidiStream = function doBidiStream(metadata) { + var listeners = { + data: [], + end: [], + status: [] + }; + var client = grpc.client(SimpleService.DoBidiStream, { + host: this.serviceHost, + metadata: metadata, + transport: this.options.transport + }); + client.onEnd(function (status, statusMessage, trailers) { + listeners.end.forEach(function (handler) { + handler(); + }); + listeners.status.forEach(function (handler) { + handler({ code: status, details: statusMessage, metadata: trailers }); + }); + listeners = null; + }); + client.onMessage(function (message) { + listeners.data.forEach(function (handler) { + handler(message); + }) + }); + client.start(metadata); + return { + on: function (type, handler) { + listeners[type].push(handler); + return this; + }, + write: function (requestMessage) { + client.send(requestMessage); + return this; + }, + end: function () { + client.finishSend(); + }, + cancel: function () { + listeners = null; + client.close(); + } + }; +}; + SimpleServiceClient.prototype.delete = function pb_delete(requestMessage, metadata, callback) { if (arguments.length === 2) { callback = arguments[1]; diff --git a/examples/generated/proto/orphan_pb_service.d.ts b/examples/generated/proto/orphan_pb_service.d.ts index 727476d8..de676141 100644 --- a/examples/generated/proto/orphan_pb_service.d.ts +++ b/examples/generated/proto/orphan_pb_service.d.ts @@ -38,6 +38,21 @@ interface ResponseStream { on(type: 'end', handler: () => void): ResponseStream; on(type: 'status', handler: (status: Status) => void): ResponseStream; } +interface RequestStream { + write(message: T): RequestStream; + end(): void; + cancel(): void; + on(type: 'end', handler: () => void): RequestStream; + on(type: 'status', handler: (status: Status) => void): RequestStream; +} +interface BidirectionalStream { + write(message: T): BidirectionalStream; + end(): void; + cancel(): void; + on(type: 'data', handler: (message: T) => void): BidirectionalStream; + on(type: 'end', handler: () => void): BidirectionalStream; + on(type: 'status', handler: (status: Status) => void): BidirectionalStream; +} export class OrphanServiceClient { readonly serviceHost: string; diff --git a/package-lock.json b/package-lock.json index 3ab159c1..c7a3432a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -302,9 +302,9 @@ "dev": true }, "grpc-web-client": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grpc-web-client/-/grpc-web-client-0.5.0.tgz", - "integrity": "sha512-AcLecuqaDp5STYXGViTQmNTCoZVfM6gi3+hvfTGXGP5YTIimASesNi39jnP8dox3x8QBelMWDdOck5/4UJaZdg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/grpc-web-client/-/grpc-web-client-0.6.2.tgz", + "integrity": "sha512-7LSp9Gr0cWLJp53ijrNW+KHNyw8rZKDMMmFKmRkxu1QJLU9vuE+5hG4NTc8ao5YwuP1rsBQi74eZ1R+sOiQVtg==", "dev": true, "requires": { "browser-headers": "0.4.0" diff --git a/package.json b/package.json index 15dc1ed1..4c4dee74 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@types/node": "^7.0.52", "babel": "^6.5.2", "chai": "^3.5.0", - "grpc-web-client": "^0.5.0", + "grpc-web-client": "^0.6.0", "lodash": "^4.17.5", "lodash.isequal": "^4.5.0", "mocha": "^5.2.0", diff --git a/proto/examplecom/simple_service.proto b/proto/examplecom/simple_service.proto index dcd5feb2..8cf2571c 100644 --- a/proto/examplecom/simple_service.proto +++ b/proto/examplecom/simple_service.proto @@ -4,7 +4,8 @@ package examplecom; import "proto/othercom/external_child_message.proto"; -// this import should not be output in the generated typescript service +// these imports should not be output in the generated typescript service +import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; message UnaryRequest { @@ -20,7 +21,9 @@ message StreamRequest { service SimpleService { rpc DoUnary(UnaryRequest) returns (othercom.ExternalChildMessage) {} - rpc DoStream(StreamRequest) returns (stream othercom.ExternalChildMessage) {} + rpc DoServerStream(StreamRequest) returns (stream othercom.ExternalChildMessage) {} + rpc DoClientStream(stream StreamRequest) returns (google.protobuf.Empty) {} + rpc DoBidiStream(stream StreamRequest) returns (stream othercom.ExternalChildMessage) {} // checks that rpc methods that use reserved JS words don't generate invalid code rpc Delete(UnaryRequest) returns (UnaryResponse) {} diff --git a/src/service/grpcweb.ts b/src/service/grpcweb.ts index fbdd87b4..b08a070f 100644 --- a/src/service/grpcweb.ts +++ b/src/service/grpcweb.ts @@ -212,6 +212,21 @@ function generateTypescriptDefinition(fileDescriptor: FileDescriptorProto, expor printer.printIndentedLn(`on(type: 'end', handler: () => void): ResponseStream;`); printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): ResponseStream;`); printer.printLn(`}`); + printer.printLn(`interface RequestStream {`); + printer.printIndentedLn(`write(message: T): RequestStream;`); + printer.printIndentedLn(`end(): void;`); + printer.printIndentedLn(`cancel(): void;`); + printer.printIndentedLn(`on(type: 'end', handler: () => void): RequestStream;`); + printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): RequestStream;`); + printer.printLn(`}`); + printer.printLn(`interface BidirectionalStream {`); + printer.printIndentedLn(`write(message: T): BidirectionalStream;`); + printer.printIndentedLn(`end(): void;`); + printer.printIndentedLn(`cancel(): void;`); + printer.printIndentedLn(`on(type: 'data', handler: (message: T) => void): BidirectionalStream;`); + printer.printIndentedLn(`on(type: 'end', handler: () => void): BidirectionalStream;`); + printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): BidirectionalStream;`); + printer.printLn(`}`); printer.printEmptyLn(); // Add a client stub that talks with the grpc-web-client library @@ -371,18 +386,96 @@ function printServerStreamStubMethod(printer: CodePrinter, method: RPCMethodDesc .dedent().printLn(`};`); } -function printBidirectionalStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) { +function printClientStreamStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) { printer - .printLn(`${method.serviceName}.prototype.${method.nameAsCamelCase} = function ${method.functionName}() {`) - .indent().printLn(`throw new Error("Client streaming is not currently supported");`) - .dedent().printLn(`}`); + .printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(metadata) {`) + .indent().printLn(`var listeners = {`) + .indent().printLn(`end: [],`) + .printLn(`status: []`) + .dedent().printLn(`};`) + .printLn(`var client = grpc.client(${method.serviceName}.${method.nameAsPascalCase}, {`) + .indent().printLn(`host: this.serviceHost,`) + .printLn(`metadata: metadata,`) + .printLn(`transport: this.options.transport`) + .dedent().printLn(`});`) + .printLn(`client.onEnd(function (status, statusMessage, trailers) {`) + .indent().printLn(`listeners.end.forEach(function (handler) {`) + .indent().printLn(`handler();`) + .dedent().printLn(`});`) + .printLn(`listeners.status.forEach(function (handler) {`) + .indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`) + .dedent().printLn(`});`) + .printLn(`listeners = null;`) + .dedent().printLn(`});`) + .printLn(`return {`) + .indent().printLn(`on: function (type, handler) {`) + .indent().printLn(`listeners[type].push(handler);`) + .printLn(`return this;`) + .dedent().printLn(`},`) + .printLn(`write: function (requestMessage) {`) + .indent().printLn(`if (!client.started) {`) + .indent().printLn(`client.start(metadata);`) + .dedent().printLn(`}`) + .printLn(`client.send(requestMessage);`) + .printLn(`return this;`) + .dedent().printLn(`},`) + .printLn(`end: function () {`) + .indent().printLn(`client.finishSend();`) + .dedent().printLn(`},`) + .printLn(`cancel: function () {`) + .indent().printLn(`listeners = null;`) + .printLn(`client.close();`) + .dedent().printLn(`}`) + .dedent().printLn(`};`) + .dedent().printLn(`};`); } -function printClientStreamStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) { +function printBidirectionalStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) { printer - .printLn(`${method.serviceName}.prototype.${method.nameAsCamelCase} = function ${method.functionName}() {`) - .indent().printLn(`throw new Error("Bi-directional streaming is not currently supported");`) - .dedent().printLn(`}`); + .printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(metadata) {`) + .indent().printLn(`var listeners = {`) + .indent().printLn(`data: [],`) + .printLn(`end: [],`) + .printLn(`status: []`) + .dedent().printLn(`};`) + .printLn(`var client = grpc.client(${method.serviceName}.${method.nameAsPascalCase}, {`) + .indent().printLn(`host: this.serviceHost,`) + .printLn(`metadata: metadata,`) + .printLn(`transport: this.options.transport`) + .dedent().printLn(`});`) + .printLn(`client.onEnd(function (status, statusMessage, trailers) {`) + .indent().printLn(`listeners.end.forEach(function (handler) {`) + .indent().printLn(`handler();`) + .dedent().printLn(`});`) + .printLn(`listeners.status.forEach(function (handler) {`) + .indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`) + .dedent().printLn(`});`) + .printLn(`listeners = null;`) + .dedent().printLn(`});`) + .printLn(`client.onMessage(function (message) {`) + .indent().printLn(`listeners.data.forEach(function (handler) {`) + .indent().printLn(`handler(message);`) + .dedent().printLn(`})`) + .dedent().printLn(`});`) + .printLn(`client.start(metadata);`) + .printLn(`return {`) + .indent().printLn(`on: function (type, handler) {`) + .indent().printLn(`listeners[type].push(handler);`) + .printLn(`return this;`) + .dedent().printLn(`},`) + .printLn(`write: function (requestMessage) {`) + .indent().printLn(`client.send(requestMessage);`) + .printLn(`return this;`) + .dedent().printLn(`},`) + .printLn(`end: function () {`) + .indent().printLn(`client.finishSend();`) + .dedent().printLn(`},`) + .printLn(`cancel: function () {`) + .indent().printLn(`listeners = null;`) + .printLn(`client.close();`) + .dedent().printLn(`}`) + .dedent().printLn(`};`) + .dedent().printLn(`};`); } function printServiceStubTypes(methodPrinter: Printer, service: RPCDescriptor) { @@ -425,10 +518,10 @@ function printServerStreamStubMethodTypes(printer: CodePrinter, method: RPCMetho printer.printLn(`${method.nameAsCamelCase}(requestMessage: ${method.requestType}, metadata?: grpc.Metadata): ResponseStream<${method.responseType}>;`); } -function printBidirectionalStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) { - printer.printLn(`${method.nameAsCamelCase}(): void;`); +function printClientStreamStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) { + printer.printLn(`${method.nameAsCamelCase}(metadata?: grpc.Metadata): RequestStream<${method.responseType}>;`); } -function printClientStreamStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) { - printer.printLn(`${method.nameAsCamelCase}(): void;`); +function printBidirectionalStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) { + printer.printLn(`${method.nameAsCamelCase}(metadata?: grpc.Metadata): BidirectionalStream<${method.responseType}>;`); } diff --git a/test/helpers/fakeGrpcTransport.ts b/test/helpers/fakeGrpcTransport.ts index 0092030d..851b9fcf 100644 --- a/test/helpers/fakeGrpcTransport.ts +++ b/test/helpers/fakeGrpcTransport.ts @@ -11,6 +11,14 @@ function frameResponse(request: Message): Uint8Array { return new Uint8Array(frame); } +export function frameRequest(request: Message): ArrayBufferView { + const bytes = request.serializeBinary(); + const frame = new ArrayBuffer(bytes.byteLength + 5); + new DataView(frame, 1, 4).setUint32(0, bytes.length, false /* big endian */); + new Uint8Array(frame, 5).set(bytes); + return new Uint8Array(frame); +} + function frameTrailers(trailers: grpc.Metadata): Uint8Array { let asString = ""; trailers.forEach((key: string, values: string[]) => { diff --git a/test/integration/service/grpcweb.ts b/test/integration/service/grpcweb.ts index e1f56865..235474c9 100644 --- a/test/integration/service/grpcweb.ts +++ b/test/integration/service/grpcweb.ts @@ -4,34 +4,55 @@ import { assert } from "chai"; import { grpc } from "grpc-web-client"; import { createContext, runInContext } from "vm"; -import { StubTransportBuilder } from "../../helpers/fakeGrpcTransport"; +import { frameRequest, StubTransportBuilder } from "../../helpers/fakeGrpcTransport"; import { ExternalChildMessage } from "../../../examples/generated/proto/othercom/external_child_message_pb"; import { SimpleService, SimpleServiceClient } from "../../../examples/generated/proto/examplecom/simple_service_pb_service"; import { StreamRequest, UnaryRequest } from "../../../examples/generated/proto/examplecom/simple_service_pb"; +import { Empty } from "google-protobuf/google/protobuf/empty_pb"; describe("service/grpc-web", () => { - it("should generate a service definition", () => { - assert.strictEqual(SimpleService.serviceName, "examplecom.SimpleService"); - - assert.strictEqual(SimpleService.DoUnary.methodName, "DoUnary"); - assert.strictEqual(SimpleService.DoUnary.service, SimpleService); - assert.strictEqual(SimpleService.DoUnary.requestStream, false); - assert.strictEqual(SimpleService.DoUnary.responseStream, false); - assert.strictEqual(SimpleService.DoUnary.requestType, UnaryRequest); - assert.strictEqual(SimpleService.DoUnary.responseType, ExternalChildMessage); - - assert.strictEqual(SimpleService.DoStream.methodName, "DoStream"); - assert.strictEqual(SimpleService.DoStream.service, SimpleService); - assert.strictEqual(SimpleService.DoStream.requestStream, false); - assert.strictEqual(SimpleService.DoStream.responseStream, true); - assert.strictEqual(SimpleService.DoStream.requestType, StreamRequest); - assert.strictEqual(SimpleService.DoStream.responseType, ExternalChildMessage); - }); + describe("generated service definitions", () => { - it("should generate service definition files for protos that have no service definitions", () => { - assert.isTrue(existsSync(resolve(__dirname, "../../../examples/generated/proto/examplecom/empty_message_no_service_pb_service.d.ts"))); - assert.isTrue(existsSync(resolve(__dirname, "../../../examples/generated/proto/examplecom/empty_message_no_service_pb_service.js"))); + it("should be exported", () => { + assert.strictEqual(SimpleService.serviceName, "examplecom.SimpleService"); + }); + + it("should contain the expected DoUnary method", () => { + assert.strictEqual(SimpleService.DoUnary.methodName, "DoUnary"); + assert.strictEqual(SimpleService.DoUnary.service, SimpleService); + assert.strictEqual(SimpleService.DoUnary.requestStream, false); + assert.strictEqual(SimpleService.DoUnary.responseStream, false); + assert.strictEqual(SimpleService.DoUnary.requestType, UnaryRequest); + assert.strictEqual(SimpleService.DoUnary.responseType, ExternalChildMessage); + }); + + it("should contain the expected DoServerStream method", () => { + assert.strictEqual(SimpleService.DoServerStream.methodName, "DoServerStream"); + assert.strictEqual(SimpleService.DoServerStream.service, SimpleService); + assert.strictEqual(SimpleService.DoServerStream.requestStream, false); + assert.strictEqual(SimpleService.DoServerStream.responseStream, true); + assert.strictEqual(SimpleService.DoServerStream.requestType, StreamRequest); + assert.strictEqual(SimpleService.DoServerStream.responseType, ExternalChildMessage); + }); + + it("should contain the expected DoClientStream method", () => { + assert.strictEqual(SimpleService.DoClientStream.methodName, "DoClientStream"); + assert.strictEqual(SimpleService.DoClientStream.service, SimpleService); + assert.strictEqual(SimpleService.DoClientStream.requestStream, true); + assert.strictEqual(SimpleService.DoClientStream.responseStream, false); + assert.strictEqual(SimpleService.DoClientStream.requestType, StreamRequest); + assert.strictEqual(SimpleService.DoClientStream.responseType, Empty); + }); + + it("should contain the expected DoClientStream method", () => { + assert.strictEqual(SimpleService.DoBidiStream.methodName, "DoBidiStream"); + assert.strictEqual(SimpleService.DoBidiStream.service, SimpleService); + assert.strictEqual(SimpleService.DoBidiStream.requestStream, true); + assert.strictEqual(SimpleService.DoBidiStream.responseStream, true); + assert.strictEqual(SimpleService.DoBidiStream.requestType, StreamRequest); + assert.strictEqual(SimpleService.DoBidiStream.responseType, ExternalChildMessage); + }); }); it("should not output imports for namespaces that are not used in the service definition", () => { @@ -42,6 +63,11 @@ describe("service/grpc-web", () => { assert.include(generatedProto, "google-protobuf/google/protobuf/timestamp_pb"); }); + it("should generate service definition files for protos that have no service definitions", () => { + assert.isTrue(existsSync(resolve(__dirname, "../../../examples/generated/proto/examplecom/empty_message_no_service_pb_service.d.ts"))); + assert.isTrue(existsSync(resolve(__dirname, "../../../examples/generated/proto/examplecom/empty_message_no_service_pb_service.js"))); + }); + it("should generate valid javascript sources", () => { const generatedService = readFileSync(resolve(__dirname, "../../../examples/generated/proto/examplecom/simple_service_pb_service.js"), "utf8"); @@ -72,14 +98,6 @@ describe("service/grpc-web", () => { assert.strictEqual(typeof sandbox.exports.SimpleService, "function"); assert.strictEqual(sandbox.exports.SimpleService.serviceName, "examplecom.SimpleService"); - assert.strictEqual(typeof sandbox.exports.SimpleService.DoStream, "object"); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.methodName, "DoStream"); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.service, sandbox.exports.SimpleService); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.requestStream, false); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.responseStream, true); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.requestType, StreamRequest); - assert.strictEqual(sandbox.exports.SimpleService.DoStream.responseType, ExternalChildMessage); - assert.strictEqual(typeof sandbox.exports.SimpleService.DoUnary, "object"); assert.strictEqual(sandbox.exports.SimpleService.DoUnary.methodName, "DoUnary"); assert.strictEqual(sandbox.exports.SimpleService.DoUnary.service, sandbox.exports.SimpleService); @@ -87,6 +105,15 @@ describe("service/grpc-web", () => { assert.strictEqual(sandbox.exports.SimpleService.DoUnary.responseStream, false); assert.strictEqual(sandbox.exports.SimpleService.DoUnary.requestType, UnaryRequest); assert.strictEqual(sandbox.exports.SimpleService.DoUnary.responseType, ExternalChildMessage); + + assert.strictEqual(typeof sandbox.exports.SimpleService.DoServerStream, "object"); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.methodName, "DoServerStream"); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.service, sandbox.exports.SimpleService); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.requestStream, false); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.responseStream, true); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.requestType, StreamRequest); + assert.strictEqual(sandbox.exports.SimpleService.DoServerStream.responseType, ExternalChildMessage); + }); describe("grpc-web service stubs", () => { @@ -111,7 +138,7 @@ describe("service/grpc-web", () => { assert.equal(client.serviceHost, "http://localhost:1", "Service host should be stored from constructor"); assert.typeOf(client.doUnary, "function", "Service should have doUnary method"); - assert.typeOf(client.doStream, "function", "Service should have doStream method"); + assert.typeOf(client.doServerStream, "function", "Service should have doServerStream method"); }); describe("unary", () => { @@ -160,6 +187,23 @@ describe("service/grpc-web", () => { }); }); + it("should send the supplied payload to the server", (done) => { + let sentMessageBytes: ArrayBufferView = new Uint8Array(0); + + const payload = new UnaryRequest(); + payload.setSomeInt64(42); + + makeClient(new StubTransportBuilder().withMessageListener(v => sentMessageBytes = v)) + .doUnary( + payload, + (err) => { + assert.ok(err === null, "should not yield an error"); + assert.deepEqual(sentMessageBytes, frameRequest(payload), "expected request message supplied to transport"); + done(); + } + ); + }); + it("should allow the caller to supply Metadata", (done) => { let sentHeaders: grpc.Metadata; @@ -175,14 +219,14 @@ describe("service/grpc-web", () => { }); }); - describe("streaming", () => { + describe("server streaming", () => { it("should route the request to the expected endpoint", (done) => { let targetUrl = ""; makeClient(new StubTransportBuilder().withRequestListener(options => targetUrl = options.url)) - .doStream(new StreamRequest()) + .doServerStream(new StreamRequest()) .on("end", () => { - assert.equal(targetUrl, "http://localhost:1/examplecom.SimpleService/DoStream"); + assert.equal(targetUrl, "http://localhost:1/examplecom.SimpleService/DoServerStream"); done(); }); }); @@ -192,7 +236,7 @@ describe("service/grpc-web", () => { let onEndInvoked = false; makeClient(new StubTransportBuilder().withMessages([payload])) - .doStream(new StreamRequest()) + .doServerStream(new StreamRequest()) .on("end", () => { onEndInvoked = true; }) .on("status", () => { assert.ok(onEndInvoked, "onEnd callback should be invoked before onStatus"); @@ -200,9 +244,9 @@ describe("service/grpc-web", () => { }); }); - it("should handle an error returned ahead of any data by the unary endpoint", (done) => { + it("should handle an error returned ahead of any data by the endpoint", (done) => { makeClient(new StubTransportBuilder().withPreMessagesError(grpc.Code.Internal, "some error")) - .doStream(new StreamRequest()) + .doServerStream(new StreamRequest()) .on("status", (status) => { assert.equal(status.code, grpc.Code.Internal, "expected grpc status code returned"); assert.equal(status.details, "some error", "expected grpc error details returned"); @@ -210,7 +254,7 @@ describe("service/grpc-web", () => { }); }); - it("should handle an error returned mid-stream by the unary endpoint", (done) => { + it("should handle an error returned mid-stream by the endpoint", (done) => { const [payload] = makePayloads("some value"); let actualData: ExternalChildMessage[] = []; @@ -218,7 +262,7 @@ describe("service/grpc-web", () => { .withMessages([payload]) .withPreTrailersError(grpc.Code.Internal, "some error") ) - .doStream(new StreamRequest()) + .doServerStream(new StreamRequest()) .on("data", payload => actualData.push(payload)) .on("status", status => { assert.equal(status.code, grpc.Code.Internal, "expected grpc status code returned"); @@ -234,7 +278,7 @@ describe("service/grpc-web", () => { let actualData: ExternalChildMessage[] = []; makeClient(new StubTransportBuilder().withMessages([payload1, payload2])) - .doStream(new StreamRequest()) + .doServerStream(new StreamRequest()) .on("data", payload => actualData.push(payload)) .on("status", status => { assert.equal(status.code, grpc.Code.OK, "status code is ok"); @@ -249,7 +293,7 @@ describe("service/grpc-web", () => { let sentHeaders: grpc.Metadata; makeClient(new StubTransportBuilder().withHeadersListener(headers => sentHeaders = headers)) - .doStream(new StreamRequest(), new grpc.Metadata({ "foo": "bar" })) + .doServerStream(new StreamRequest(), new grpc.Metadata({ "foo": "bar" })) .on("end", () => { assert.deepEqual(sentHeaders.get("foo"), ["bar"]); done(); @@ -257,8 +301,11 @@ describe("service/grpc-web", () => { }); it("should allow the caller to cancel the request", (done) => { + let cancelInvoked = false; + const transport = new StubTransportBuilder() .withMessages(makePayloads("foo", "bar")) + .withCancelListener(() => cancelInvoked = true) .withManualTrigger() .build(); @@ -267,7 +314,7 @@ describe("service/grpc-web", () => { let onEndFired = false; let onStatusFired = false; - const handle = client.doStream(new StreamRequest()) + const handle = client.doServerStream(new StreamRequest()) .on("data", () => messageCount++) .on("end", () => onEndFired = true) .on("status", () => onStatusFired = true); @@ -278,6 +325,7 @@ describe("service/grpc-web", () => { transport.sendTrailers(); setTimeout(() => { + assert.equal(cancelInvoked, true, "the Transport should have been cancelled by the client"); assert.equal(messageCount, 0, "invocation cancelled before any messages were sent"); assert.equal(onEndFired, false, "'end' should not have fired when the invocation is cancelled"); assert.equal(onStatusFired, false, "'status' should not have fired when the invocation is cancelled"); @@ -286,6 +334,262 @@ describe("service/grpc-web", () => { }); }); + describe("client streaming", () => { + const [ payload ] = makePayloads("some value"); + + it("should route the request to the expected endpoint", (done) => { + let targetUrl = ""; + + makeClient(new StubTransportBuilder().withRequestListener(options => targetUrl = options.url)) + .doClientStream() + .on("end", () => { + assert.equal(targetUrl, "http://localhost:1/examplecom.SimpleService/DoClientStream"); + done(); + }) + .write(payload) + .end(); + }); + + it("should close the connection when end() is invoked", (done) => { + let finishSendInvoked = false; + makeClient(new StubTransportBuilder().withFinishSendListener(() => finishSendInvoked = true)) + .doClientStream() + .on("end", () => { + assert.ok(finishSendInvoked); + done(); + }) + .write(payload) + .end(); + }); + + it("should invoke onEnd before onStatus", (done) => { + let onEndInvoked = false; + + makeClient(new StubTransportBuilder()) + .doClientStream() + .on("end", () => { onEndInvoked = true; }) + .on("status", () => { + assert.ok(onEndInvoked, "onEnd callback should be invoked before onStatus"); + done(); + }) + .write(payload) + .end(); + }); + + it("should handle an error returned ahead of any data by the server", (done) => { + makeClient(new StubTransportBuilder().withPreMessagesError(grpc.Code.Internal, "some error")) + .doClientStream() + .on("status", (status) => { + assert.equal(status.code, grpc.Code.Internal, "expected grpc status code returned"); + assert.equal(status.details, "some error", "expected grpc error details returned"); + done(); + }) + .write(payload) + .end(); + }); + + it("should allow the caller to supply multiple messages", (done) => { + const [ reqMsgOne, reqMsgTwo ] = makePayloads("one", "two"); + const sentMessageBytes: ArrayBufferView[] = []; + + makeClient(new StubTransportBuilder().withMessageListener(v => { sentMessageBytes.push(v); })) + .doClientStream() + .on("end", () => { + assert.equal(sentMessageBytes.length, 2, "Two messages are sent"); + assert.deepEqual(sentMessageBytes[0], frameRequest(reqMsgOne)); + assert.deepEqual(sentMessageBytes[1], frameRequest(reqMsgTwo)); + done(); + }) + .write(reqMsgOne) + .write(reqMsgTwo) + .end(); + }); + + it("should allow the caller to supply Metadata", (done) => { + let sentHeaders: grpc.Metadata; + + makeClient(new StubTransportBuilder().withHeadersListener(headers => sentHeaders = headers)) + .doClientStream(new grpc.Metadata({ "foo": "bar" })) + .on("end", () => { + assert.deepEqual(sentHeaders.get("foo"), ["bar"]); + done(); + }) + .write(payload) + .end(); + }); + + it("should allow the caller to cancel the request", (done) => { + let cancelInvoked = true; + + const transport = new StubTransportBuilder() + .withCancelListener(() => cancelInvoked = true) + .withManualTrigger() + .build(); + + const client = new SimpleServiceClient("http://localhost:1", { transport }); + let onEndFired = false; + let onStatusFired = false; + + const handle = client.doClientStream() + .on("end", () => onEndFired = true) + .on("status", () => onStatusFired = true) + .write(payload); + + transport.sendHeaders(); + handle.cancel(); + transport.sendTrailers(); + + setTimeout(() => { + assert.equal(cancelInvoked, true, "the Transport should have been cancelled by the client"); + assert.equal(onEndFired, false, "'end' should not have fired when the invocation is cancelled"); + assert.equal(onStatusFired, false, "'status' should not have fired when the invocation is cancelled"); + done(); + }, 20); + }); + }); + + describe("bidirectional streaming", () => { + const [ payload ] = makePayloads("some value"); + + it("should route the request to the expected endpoint", (done) => { + let targetUrl = ""; + + makeClient(new StubTransportBuilder().withRequestListener(options => targetUrl = options.url)) + .doBidiStream() + .on("end", () => { + assert.equal(targetUrl, "http://localhost:1/examplecom.SimpleService/DoBidiStream"); + done(); + }) + .end(); + }); + + it("should invoke onEnd before onStatus if the client ends the stream", (done) => { + let onEndInvoked = false; + + makeClient(new StubTransportBuilder()) + .doBidiStream() + .on("end", () => { onEndInvoked = true; }) + .on("status", () => { + assert.ok(onEndInvoked, "onEnd callback should be invoked before onStatus"); + done(); + }) + .write(payload) + .end(); + }); + + it("should close the connection when end() is invoked", (done) => { + let finishSendInvoked = false; + makeClient(new StubTransportBuilder().withFinishSendListener(() => finishSendInvoked = true)) + .doBidiStream() + .on("end", () => { + assert.ok(finishSendInvoked); + done(); + }) + .write(payload) + .end(); + }); + + it("should invoke onEnd before onStatus if the server ends the stream", (done) => { + let onEndInvoked = false; + + makeClient(new StubTransportBuilder().withMessages([ payload ])) + .doBidiStream() + .on("end", () => { onEndInvoked = true; }) + .on("status", () => { + assert.ok(onEndInvoked, "onEnd callback should be invoked before onStatus"); + done(); + }); + }); + + it("should handle an error returned ahead of any data by the server", (done) => { + makeClient(new StubTransportBuilder().withPreMessagesError(grpc.Code.Internal, "some error")) + .doClientStream() + .on("status", (status) => { + assert.equal(status.code, grpc.Code.Internal, "expected grpc status code returned"); + assert.equal(status.details, "some error", "expected grpc error details returned"); + done(); + }) + .write(payload); + }); + + it("should handle an error returned mid-stream by the server", (done) => { + let actualData: ExternalChildMessage[] = []; + + makeClient(new StubTransportBuilder() + .withMessages([payload]) + .withPreTrailersError(grpc.Code.Internal, "some error") + ) + .doBidiStream() + .on("data", payload => actualData.push(payload)) + .on("status", status => { + assert.equal(status.code, grpc.Code.Internal, "expected grpc status code returned"); + assert.equal(status.details, "some error", "expected grpc error details returned"); + assert.equal(actualData.length, 1, "messages sent by the server, ahead of any error are exposed"); + assert.equal(actualData[0].getMyString(), "some value", "payload is well formed"); + done(); + }); + }); + + it("should allow the caller to supply multiple messages", (done) => { + const [ reqMsgOne, reqMsgTwo ] = makePayloads("one", "two"); + const sentMessageBytes: ArrayBufferView[] = []; + + makeClient(new StubTransportBuilder().withMessageListener(v => { sentMessageBytes.push(v); })) + .doBidiStream() + .on("end", () => { + assert.equal(sentMessageBytes.length, 2, "Two messages are sent"); + assert.deepEqual(sentMessageBytes[0], frameRequest(reqMsgOne)); + assert.deepEqual(sentMessageBytes[1], frameRequest(reqMsgTwo)); + done(); + }) + .write(reqMsgOne) + .write(reqMsgTwo) + .end(); + }); + + it("should allow the caller to supply Metadata", (done) => { + let sentHeaders: grpc.Metadata; + + makeClient(new StubTransportBuilder().withHeadersListener(headers => sentHeaders = headers)) + .doBidiStream(new grpc.Metadata({ "foo": "bar" })) + .on("end", () => { + assert.deepEqual(sentHeaders.get("foo"), ["bar"]); + done(); + }) + .write(payload) + .end(); + }); + + it("should allow the caller to cancel the request", (done) => { + let cancelInvoked = false; + + const transport = new StubTransportBuilder() + .withManualTrigger() + .withCancelListener(() => cancelInvoked = true) + .build(); + + const client = new SimpleServiceClient("http://localhost:1", { transport }); + let onEndFired = false; + let onStatusFired = false; + + const handle = client.doBidiStream() + .on("end", () => onEndFired = true) + .on("status", () => onStatusFired = true) + .write(payload); + + transport.sendHeaders(); + handle.cancel(); + transport.sendTrailers(); + + setTimeout(() => { + assert.equal(cancelInvoked, true, "the Transport should have been cancelled by the client"); + assert.equal(onEndFired, false, "'end' should not have fired when the invocation is cancelled"); + assert.equal(onStatusFired, false, "'status' should not have fired when the invocation is cancelled"); + done(); + }, 20); + }); + }); + describe("methods named using reserved words", () => { it("should route the request to the expected endpoint", () => { const client = new SimpleServiceClient("http://localhost:1"); diff --git a/test/mocha-run-suite.sh b/test/mocha-run-suite.sh index e8e35a67..63be4051 100755 --- a/test/mocha-run-suite.sh +++ b/test/mocha-run-suite.sh @@ -12,11 +12,16 @@ if [[ -z "${TEST_SUITE}" ]]; then exit 1 fi +if [[ "x${MOCHA_DEBUG}" != "x" ]]; then + MOCHA_DEBUG="--inspect-brk" +fi + mocha \ --reporter mocha-spec-json-output-reporter \ - --reporter-options fileName=./test/mocha-report.json \ + --reporter-options "fileName=./test/mocha-report.json" \ --require ts-node/register/type-check \ --require source-map-support/register \ + ${MOCHA_DEBUG} \ "${TEST_SUITE}" node ./test/mocha-check-report ./test/mocha-report.json From 1d7e31036e910e4d19eec02fef8d78875c7d3938 Mon Sep 17 00:00:00 2001 From: John Reeves Date: Sat, 8 Sep 2018 10:56:39 +0100 Subject: [PATCH 03/15] Collapse Bazel Instructions in README (#106) * Update README.md * Update README.md --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad64ccfa..1c09b413 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,10 @@ npm install ts-protoc-gen@next ``` ### bazel +
Instructions for using ts-protoc-gen within a bazel build environment

+ Include the following in your `WORKSPACE`: + ```python git_repository( name = "io_bazel_rules_go", @@ -77,7 +80,8 @@ load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") ts_setup_workspace() ``` -Now in your `BUILD.bazel`: +Now in your `BUILD.bazel`: + ```python load("@ts_protoc_gen//:defs.bzl", "typescript_proto_library") @@ -97,7 +101,9 @@ typescript_proto_library( name = "generate", proto = ":proto", ) -``` +``` + +

## Contributing Contributions are welcome! Please refer to [CONTRIBUTING.md](https://github.com/improbable-eng/ts-protoc-gen/blob/master/CONTRIBUTING.md) for more information. @@ -181,4 +187,4 @@ By default the google-protobuf library will use the JavaScript number type to st message Example { uint64 bigInt = 1 [jstype = JS_STRING]; } -``` \ No newline at end of file +``` From 5b06c73bf40ac27a114b902cddf168ecbf7f9ce0 Mon Sep 17 00:00:00 2001 From: John Reeves Date: Sat, 8 Sep 2018 10:56:53 +0100 Subject: [PATCH 04/15] Add hint for Windows users (#107) Hint that the path to ts-protoc-gen must be absolute on Windows. Fixes #100 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c09b413..cb724bcc 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ msg.setName("John Doe"); 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: ``` -# Path to this plugin +# 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" # Directory to write generated code to (.js and .d.ts files) From 687bcc79ea0a33cdff1ae8c424b70c1fa8fc0191 Mon Sep 17 00:00:00 2001 From: Jonny Reeves Date: Wed, 12 Sep 2018 18:13:12 +0100 Subject: [PATCH 05/15] Replace usage of `Object.assign` (#110) The grpc-web service stub code generated by ts-protoc-gen is invoking an ES6 function (`Object.assign`) which is tricking webpack into treating the modules as ES6 modules instead of ES5 modules. Replacing the `Object.assign` invocation with some vanilla ES5 results in webpack correctly identify the module as a CommonJS module and thereby exposing the `exports` object to the scope of the function. Fixes #103 --- .../proto/examplecom/simple_service_pb_service.js | 10 ++++++++-- examples/generated/proto/orphan_pb_service.js | 5 ++++- src/service/grpcweb.ts | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.js b/examples/generated/proto/examplecom/simple_service_pb_service.js index 14a5a407..bee0f019 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.js +++ b/examples/generated/proto/examplecom/simple_service_pb_service.js @@ -77,7 +77,10 @@ SimpleServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat onEnd: function (response) { if (callback) { if (response.status !== grpc.Code.OK) { - callback(Object.assign(new Error(response.statusMessage), { code: response.status, metadata: response.trailers }), null); + var err = new Error(response.statusMessage); + err.code = response.status; + err.metadata = response.trailers; + callback(err, null); } else { callback(null, response.message); } @@ -224,7 +227,10 @@ SimpleServiceClient.prototype.delete = function pb_delete(requestMessage, metada onEnd: function (response) { if (callback) { if (response.status !== grpc.Code.OK) { - callback(Object.assign(new Error(response.statusMessage), { code: response.status, metadata: response.trailers }), null); + var err = new Error(response.statusMessage); + err.code = response.status; + err.metadata = response.trailers; + callback(err, null); } else { callback(null, response.message); } diff --git a/examples/generated/proto/orphan_pb_service.js b/examples/generated/proto/orphan_pb_service.js index 979be1ea..f2f6c77e 100644 --- a/examples/generated/proto/orphan_pb_service.js +++ b/examples/generated/proto/orphan_pb_service.js @@ -48,7 +48,10 @@ OrphanServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat onEnd: function (response) { if (callback) { if (response.status !== grpc.Code.OK) { - callback(Object.assign(new Error(response.statusMessage), { code: response.status, metadata: response.trailers }), null); + var err = new Error(response.statusMessage); + err.code = response.status; + err.metadata = response.trailers; + callback(err, null); } else { callback(null, response.message); } diff --git a/src/service/grpcweb.ts b/src/service/grpcweb.ts index b08a070f..d79b1af8 100644 --- a/src/service/grpcweb.ts +++ b/src/service/grpcweb.ts @@ -334,7 +334,10 @@ function printUnaryStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) .printLn(`onEnd: function (response) {`) .indent().printLn(`if (callback) {`) .indent().printLn(`if (response.status !== grpc.Code.OK) {`) - .indent().printLn(`callback(Object.assign(new Error(response.statusMessage), { code: response.status, metadata: response.trailers }), null);`) + .indent().printLn(`var err = new Error(response.statusMessage);`) + .printLn(`err.code = response.status;`) + .printLn(`err.metadata = response.trailers;`) + .printLn(`callback(err, null);`) .dedent().printLn(`} else {`) .indent().printLn(`callback(null, response.message);`) .dedent().printLn(`}`) From 67e0c93fa4e29539ec57c97d23116f366ffb94e7 Mon Sep 17 00:00:00 2001 From: John Reeves Date: Thu, 13 Sep 2018 09:33:11 +0100 Subject: [PATCH 06/15] Be explicit about the need for a CommonJS environment. (#108) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb724bcc..3b11f207 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,9 @@ protoc \ users.proto base.proto ``` -The `generated` folder will now contain both `pb_service.js` and `pb_service.d.ts` files which you can reference in your TypeScript project to make RPCs. +The `generated` folder will now contain both `pb_service.js` and `pb_service.d.ts` files which you can reference in your TypeScript project to make RPCs. +**Note** Note that these modules require a CommonJS environment. If you intend to consume these stubs in a browser environment you will need to use a module bundler such as [webpack](https://webpack.js.org/). **Note** Both `js` and `d.ts` service files will be generated regardless of whether there are service definitions in the proto files. ```js From 8ca48fe12aee3fbf4da3f12a82d21ce2a50678c3 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 24 Sep 2018 00:57:20 -0700 Subject: [PATCH 07/15] Make ServiceError optionally null (#116) * Make ServiceError optionally null * Unwrap optional error in tests --- .../proto/examplecom/simple_service_pb_service.d.ts | 8 ++++---- examples/generated/proto/orphan_pb_service.d.ts | 4 ++-- src/service/grpcweb.ts | 4 ++-- test/integration/service/grpcweb.ts | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts index cf7f30c8..4c48a3e7 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts +++ b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts @@ -93,11 +93,11 @@ export class SimpleServiceClient { doUnary( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, metadata: grpc.Metadata, - callback: (error: ServiceError, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void + callback: (error: ServiceError|null, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void ): void; doUnary( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, - callback: (error: ServiceError, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void + callback: (error: ServiceError|null, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void ): void; doServerStream(requestMessage: proto_examplecom_simple_service_pb.StreamRequest, metadata?: grpc.Metadata): ResponseStream; doClientStream(metadata?: grpc.Metadata): RequestStream; @@ -105,11 +105,11 @@ export class SimpleServiceClient { delete( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, metadata: grpc.Metadata, - callback: (error: ServiceError, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void + callback: (error: ServiceError|null, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void ): void; delete( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, - callback: (error: ServiceError, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void + callback: (error: ServiceError|null, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void ): void; } diff --git a/examples/generated/proto/orphan_pb_service.d.ts b/examples/generated/proto/orphan_pb_service.d.ts index de676141..eb66da46 100644 --- a/examples/generated/proto/orphan_pb_service.d.ts +++ b/examples/generated/proto/orphan_pb_service.d.ts @@ -61,11 +61,11 @@ export class OrphanServiceClient { doUnary( requestMessage: proto_orphan_pb.OrphanUnaryRequest, metadata: grpc.Metadata, - callback: (error: ServiceError, responseMessage: proto_orphan_pb.OrphanMessage|null) => void + callback: (error: ServiceError|null, responseMessage: proto_orphan_pb.OrphanMessage|null) => void ): void; doUnary( requestMessage: proto_orphan_pb.OrphanUnaryRequest, - callback: (error: ServiceError, responseMessage: proto_orphan_pb.OrphanMessage|null) => void + callback: (error: ServiceError|null, responseMessage: proto_orphan_pb.OrphanMessage|null) => void ): void; doStream(requestMessage: proto_orphan_pb.OrphanStreamRequest, metadata?: grpc.Metadata): ResponseStream; } diff --git a/src/service/grpcweb.ts b/src/service/grpcweb.ts index d79b1af8..baa49256 100644 --- a/src/service/grpcweb.ts +++ b/src/service/grpcweb.ts @@ -509,11 +509,11 @@ function printUnaryStubMethodTypes(printer: CodePrinter, method: RPCMethodDescri .printLn(`${method.nameAsCamelCase}(`) .indent().printLn(`requestMessage: ${method.requestType},`) .printLn(`metadata: grpc.Metadata,`) - .printLn(`callback: (error: ServiceError, responseMessage: ${method.responseType}|null) => void`) + .printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`) .dedent().printLn(`): void;`) .printLn(`${method.nameAsCamelCase}(`) .indent().printLn(`requestMessage: ${method.requestType},`) - .printLn(`callback: (error: ServiceError, responseMessage: ${method.responseType}|null) => void`) + .printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`) .dedent().printLn(`): void;`); } diff --git a/test/integration/service/grpcweb.ts b/test/integration/service/grpcweb.ts index 235474c9..6faedc33 100644 --- a/test/integration/service/grpcweb.ts +++ b/test/integration/service/grpcweb.ts @@ -166,9 +166,9 @@ describe("service/grpc-web", () => { assert.ok(error !== null && typeof error === "object", "should yield an error"); assert.ok(response === null, "should yield null instead of a response"); - assert.equal(error.message, "some internal error", "should expose the grpc error message (.message)"); - assert.equal(error.code, 13, "should expose the grpc status code (.code)"); - assert.ok(error.metadata instanceof grpc.Metadata, "should expose the trailing response metadata (.metadata)"); + assert.equal(error!.message, "some internal error", "should expose the grpc error message (.message)"); + assert.equal(error!.code, 13, "should expose the grpc status code (.code)"); + assert.ok(error!.metadata instanceof grpc.Metadata, "should expose the trailing response metadata (.metadata)"); done(); }); }); From 34d3d68cd597eacec5c9aec075dd9bd74e190cc2 Mon Sep 17 00:00:00 2001 From: Riku Inoue Date: Sat, 13 Oct 2018 17:30:00 +0900 Subject: [PATCH 08/15] Fix snake_cased oneof message are generated to incorrect types (#118) * Fix oneOfName() returns incorrect type names * Add integration tests --- .../proto/examplecom/oneof_message_pb.d.ts | 35 +++ .../proto/examplecom/oneof_message_pb.js | 224 ++++++++++++++++++ proto/examplecom/oneof_message.proto | 7 + src/util.ts | 2 +- test/integration/oneof.ts | 3 +- test/unit/util.ts | 20 +- 6 files changed, 288 insertions(+), 3 deletions(-) diff --git a/examples/generated/proto/examplecom/oneof_message_pb.d.ts b/examples/generated/proto/examplecom/oneof_message_pb.d.ts index c641ba6a..0a613e00 100644 --- a/examples/generated/proto/examplecom/oneof_message_pb.d.ts +++ b/examples/generated/proto/examplecom/oneof_message_pb.d.ts @@ -108,3 +108,38 @@ export namespace CamelCasedOneOfMessage { } } +export class SnakeCasedOneOfMessage extends jspb.Message { + hasAnint(): boolean; + clearAnint(): void; + getAnint(): number; + setAnint(value: number): void; + + hasThestring(): boolean; + clearThestring(): void; + getThestring(): string; + setThestring(value: string): void; + + getSnakeCasedMessageCase(): SnakeCasedOneOfMessage.SnakeCasedMessageCase; + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SnakeCasedOneOfMessage.AsObject; + static toObject(includeInstance: boolean, msg: SnakeCasedOneOfMessage): SnakeCasedOneOfMessage.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SnakeCasedOneOfMessage, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SnakeCasedOneOfMessage; + static deserializeBinaryFromReader(message: SnakeCasedOneOfMessage, reader: jspb.BinaryReader): SnakeCasedOneOfMessage; +} + +export namespace SnakeCasedOneOfMessage { + export type AsObject = { + anint: number, + thestring: string, + } + + export enum SnakeCasedMessageCase { + SNAKE_CASED_MESSAGE_NOT_SET = 0, + ANINT = 1, + THESTRING = 2, + } +} + diff --git a/examples/generated/proto/examplecom/oneof_message_pb.js b/examples/generated/proto/examplecom/oneof_message_pb.js index 56b080bf..38e8f1f1 100644 --- a/examples/generated/proto/examplecom/oneof_message_pb.js +++ b/examples/generated/proto/examplecom/oneof_message_pb.js @@ -15,6 +15,7 @@ var proto_othercom_external_child_message_pb = require('../../proto/othercom/ext goog.exportSymbol('proto.examplecom.CamelCasedOneOfMessage', null, global); goog.exportSymbol('proto.examplecom.OneOfMessage', null, global); goog.exportSymbol('proto.examplecom.OneOfMessage.InternalChildMessage', null, global); +goog.exportSymbol('proto.examplecom.SnakeCasedOneOfMessage', null, global); /** * Generated by JsPbCodeGenerator. @@ -693,4 +694,227 @@ proto.examplecom.CamelCasedOneOfMessage.prototype.hasThestring = function() { }; + +/** + * 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.SnakeCasedOneOfMessage = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_); +}; +goog.inherits(proto.examplecom.SnakeCasedOneOfMessage, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.examplecom.SnakeCasedOneOfMessage.displayName = 'proto.examplecom.SnakeCasedOneOfMessage'; +} +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_ = [[1,2]]; + +/** + * @enum {number} + */ +proto.examplecom.SnakeCasedOneOfMessage.SnakeCasedMessageCase = { + SNAKE_CASED_MESSAGE_NOT_SET: 0, + ANINT: 1, + THESTRING: 2 +}; + +/** + * @return {proto.examplecom.SnakeCasedOneOfMessage.SnakeCasedMessageCase} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.getSnakeCasedMessageCase = function() { + return /** @type {proto.examplecom.SnakeCasedOneOfMessage.SnakeCasedMessageCase} */(jspb.Message.computeOneofCase(this, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_[0])); +}; + + + +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_, 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.SnakeCasedOneOfMessage.prototype.toObject = function(opt_includeInstance) { + return proto.examplecom.SnakeCasedOneOfMessage.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.SnakeCasedOneOfMessage} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.examplecom.SnakeCasedOneOfMessage.toObject = function(includeInstance, msg) { + var f, obj = { + anint: jspb.Message.getFieldWithDefault(msg, 1, 0), + thestring: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + 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.SnakeCasedOneOfMessage} + */ +proto.examplecom.SnakeCasedOneOfMessage.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.examplecom.SnakeCasedOneOfMessage; + return proto.examplecom.SnakeCasedOneOfMessage.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.examplecom.SnakeCasedOneOfMessage} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.examplecom.SnakeCasedOneOfMessage} + */ +proto.examplecom.SnakeCasedOneOfMessage.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt32()); + msg.setAnint(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setThestring(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.examplecom.SnakeCasedOneOfMessage.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.SnakeCasedOneOfMessage} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.examplecom.SnakeCasedOneOfMessage.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = /** @type {number} */ (jspb.Message.getField(message, 1)); + if (f != null) { + writer.writeInt32( + 1, + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 2)); + if (f != null) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional int32 anint = 1; + * @return {number} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.getAnint = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** @param {number} value */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.setAnint = function(value) { + jspb.Message.setOneofField(this, 1, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_[0], value); +}; + + +proto.examplecom.SnakeCasedOneOfMessage.prototype.clearAnint = function() { + jspb.Message.setOneofField(this, 1, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.hasAnint = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string theString = 2; + * @return {string} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.getThestring = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.setThestring = function(value) { + jspb.Message.setOneofField(this, 2, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_[0], value); +}; + + +proto.examplecom.SnakeCasedOneOfMessage.prototype.clearThestring = function() { + jspb.Message.setOneofField(this, 2, proto.examplecom.SnakeCasedOneOfMessage.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.examplecom.SnakeCasedOneOfMessage.prototype.hasThestring = function() { + return jspb.Message.getField(this, 2) != null; +}; + + goog.object.extend(exports, proto.examplecom); diff --git a/proto/examplecom/oneof_message.proto b/proto/examplecom/oneof_message.proto index ba23d676..e3a6fb68 100644 --- a/proto/examplecom/oneof_message.proto +++ b/proto/examplecom/oneof_message.proto @@ -23,3 +23,10 @@ message CamelCasedOneOfMessage { string theString = 2; } } + +message SnakeCasedOneOfMessage { + oneof snake_cased_message { + int32 anint = 1; + string theString = 2; + } +} diff --git a/src/util.ts b/src/util.ts index d5639f20..c82df309 100644 --- a/src/util.ts +++ b/src/util.ts @@ -21,7 +21,7 @@ export function isProto2(fileDescriptor: FileDescriptorProto): boolean { } export function oneOfName(name: string) { - return uppercaseFirst(name.toLowerCase()); + return uppercaseFirst(snakeToCamel(name.toLowerCase())); } export function generateIndent(indentLevel: number): string { diff --git a/test/integration/oneof.ts b/test/integration/oneof.ts index d720f503..7a260833 100644 --- a/test/integration/oneof.ts +++ b/test/integration/oneof.ts @@ -1,5 +1,5 @@ import {assert} from "chai"; -import {OneOfMessage, CamelCasedOneOfMessage} from "../../examples/generated/proto/examplecom/oneof_message_pb"; +import {OneOfMessage, CamelCasedOneOfMessage, SnakeCasedOneOfMessage} from "../../examples/generated/proto/examplecom/oneof_message_pb"; import {ExternalChildMessage} from "../../examples/generated/proto/othercom/external_child_message_pb"; import InternalChildMessage = OneOfMessage.InternalChildMessage; @@ -79,5 +79,6 @@ describe("oneofs", () => { it("should handle casing inconsistencies present in protoc-gen's javascript implementation (see #63)", () => { assert.strictEqual(typeof CamelCasedOneOfMessage.CamelcasedmessageCase, "object"); + assert.strictEqual(typeof SnakeCasedOneOfMessage.SnakeCasedMessageCase, "object"); }); }); diff --git a/test/unit/util.ts b/test/unit/util.ts index edc9ed50..90c5c77f 100644 --- a/test/unit/util.ts +++ b/test/unit/util.ts @@ -1,5 +1,5 @@ import {assert} from "chai"; -import {replaceProtoSuffix} from "../../src/util"; +import {oneOfName, replaceProtoSuffix} from "../../src/util"; describe("util", () => { @@ -30,4 +30,22 @@ describe("util", () => { }); }); + describe("oneOfName", () => { + [{ + in: "one_of_name", + out: "OneOfName", + }, { + in: "ONE_OF_NAME", + out: "OneOfName", + }, { + in: "OneOfName", + out: "Oneofname" + }].forEach(senario => { + it(`should map '${senario.in}' to '${senario.out}'`, () => { + const actual = oneOfName(senario.in); + assert.equal(actual, senario.out); + }); + }); + }); + }); From 0553580cc3911070d20bfd81420b570e7fb8911e Mon Sep 17 00:00:00 2001 From: John Reeves Date: Sun, 14 Oct 2018 10:21:28 +0100 Subject: [PATCH 09/15] Don't deploy .deb files to npm (#121) Fixes #117 --- .npmignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index ac499fba..83e0f6e0 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ test/ src/ .* +*.deb release.sh generate.sh -examples/ \ No newline at end of file +examples/ From d9ec4848fbf85f8f64907941c34be8032015511a Mon Sep 17 00:00:00 2001 From: John Reeves Date: Sun, 14 Oct 2018 12:22:43 +0100 Subject: [PATCH 10/15] Clean up Bazel Installation during Travis Build (#122) * Clean up Bazel Installation during Travis Build * Fixup build steps * Remove trailing slashes * Remove bazel deb :P --- .npmignore | 1 - .travis.yml | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.npmignore b/.npmignore index 83e0f6e0..b7367084 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,6 @@ test/ src/ .* -*.deb release.sh generate.sh examples/ diff --git a/.travis.yml b/.travis.yml index 769678e9..e2c4ac54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,11 @@ cache: directories: - node_modules before_install: -- wget https://github.com/bazelbuild/bazel/releases/download/0.11.0/bazel_0.11.0-linux-x86_64.deb -- sudo dpkg -i bazel_0.11.0-linux-x86_64.deb +- BAZEL_VERSION=0.11.0 +- BAZEL_DEB="bazel_${BAZEL_VERSION}-linux-x86_64.deb" +- wget https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/${BAZEL_DEB} -O "${BAZEL_DEB}" +- sudo dpkg -i ${BAZEL_DEB} +- rm ${BAZEL_DEB} install: - npm install script: From ef031bfd5ed9ee08b35367e6c436d2fb80e46084 Mon Sep 17 00:00:00 2001 From: Jonny Reeves Date: Sun, 14 Oct 2018 12:29:16 +0100 Subject: [PATCH 11/15] Prepare 0.7.7 release (#112) * Prepare 0.7.7 release * Update changelog --- CHANGELOG.md | 12 +++++++++++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5886449..b9712ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -## 0.7.7-pre +## 0.7.7 + +### Fixes +* Replace usage of `Object.assign` to fix webpack issue. [@jonny-improbable](https://github.com/jonny-improbable) in [#110](https://github.com/improbable-eng/ts-protoc-gen/pull/110) +* Errors returned by Unary Services should be optionally null. [@colinking](https://github.com/collinking) in [#116](https://github.com/improbable-eng/ts-protoc-gen/pull/116) +* Fix snake_cased oneof message are generated to incorrect types. [@riku179](https://github.com/riku179) in [#118](https://github.com/improbable-eng/ts-protoc-gen/pull/118) +* `.deb` artificats being deployment to npm. [@jonnyreeves](https://github.com/jonnyreeves) in [#121](https://github.com/improbable-eng/ts-protoc-gen/pull/121) + +### Changes +* Add support for `jstype` proto annotations. [@jonny-improbable](https://github.com/jonny-improbable) in [#104](https://github.com/improbable-eng/ts-protoc-gen/pull/104) +* Implement Client Streaming and BiDi Streaming for grpc-web service stubs. [@jonnyreeves](https://github.com/jonnyreeves) in [#82](https://github.com/improbable-eng/ts-protoc-gen/pull/82) ## 0.7.6 diff --git a/package-lock.json b/package-lock.json index c7a3432a..a1dfc8d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ts-protoc-gen", - "version": "0.7.7-pre", + "version": "0.7.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4c4dee74..fb9d8067 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-protoc-gen", - "version": "0.7.7-pre", + "version": "0.7.7", "description": "Protoc Plugin for TypeScript Declarations and Service Definitions", "scripts": { "lint": "tslint -c tslint.json 'test/**/*.ts' 'src/**/*.ts'", From 0b5d779568338981c9fc016a2cd5e9cedadce1f1 Mon Sep 17 00:00:00 2001 From: John Reeves Date: Sun, 14 Oct 2018 14:48:28 +0100 Subject: [PATCH 12/15] Bumped to v0.7.8-pre (#123) --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9712ac4..4998db92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.7.8-pre + ## 0.7.7 ### Fixes diff --git a/package.json b/package.json index fb9d8067..351f7708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-protoc-gen", - "version": "0.7.7", + "version": "0.7.8-pre", "description": "Protoc Plugin for TypeScript Declarations and Service Definitions", "scripts": { "lint": "tslint -c tslint.json 'test/**/*.ts' 'src/**/*.ts'", From b7267ea97c2db5a86cac4c93a9ee2148bad0810b Mon Sep 17 00:00:00 2001 From: Jonny Reeves Date: Tue, 16 Oct 2018 15:04:06 +0100 Subject: [PATCH 13/15] CI should check that generated code has been committed (#127) * CI should check that generated code has been committed Add a travis build step which re-generates the generated code and then checks for changes to the workspce. If any are detected it will fail the build and prompt the user to run the `./generate.sh` script. * Patch up generation script so it can be invoked from another script. * Use unzip instead of tar. --- generate.sh | 8 ++++++-- package-lock.json | 2 +- travis-ci-build.sh | 8 ++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/generate.sh b/generate.sh index 0e22382a..b10b1b22 100755 --- a/generate.sh +++ b/generate.sh @@ -24,14 +24,18 @@ PROTOC_VERSION="3.5.1" echo "Downloading protoc v${PROTOC_VERSION} for ${platform}..." mkdir -p protoc if [[ $platform == 'Linux' ]]; then - curl -L https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip | tar xz -C protoc + PROTOC_URL="https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip" elif [[ $platform == 'Mac' ]]; then - curl -L https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-osx-x86_64.zip | tar xz -C protoc + PROTOC_URL="https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-osx-x86_64.zip" else echo "Cannot download protoc. ${platform} is not currently supported by ts-protoc-gen" exit 1 fi +wget ${PROTOC_URL} --output-document="protoc-${PROTOC_VERSION}.zip" +unzip "protoc-${PROTOC_VERSION}.zip" -d protoc +rm "protoc-${PROTOC_VERSION}.zip" + PROTOC=./protoc/bin/protoc echo "Generating proto definitions..." diff --git a/package-lock.json b/package-lock.json index a1dfc8d9..a6eb776b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ts-protoc-gen", - "version": "0.7.7", + "version": "0.7.8-pre", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/travis-ci-build.sh b/travis-ci-build.sh index d8c3a26b..c81fba63 100755 --- a/travis-ci-build.sh +++ b/travis-ci-build.sh @@ -3,4 +3,12 @@ set -ex npm run lint npm test + +./generate.sh +MODIFIED_FILES=$(git diff --name-only) +if [[ -n $MODIFIED_FILES ]]; then + echo "ERROR: Changes detected in generated code, please run './generate.sh' and check-in the changes." + exit 1 +fi + bazel build //...:all \ No newline at end of file From 7846e2f17566a8650208578679a9128bb3e072a0 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 16 Oct 2018 15:54:53 -0400 Subject: [PATCH 14/15] Allow canceling unary calls (#124) * Allow canceling unary calls - Fixes https://github.com/improbable-eng/grpc-web/issues/252 * Update examples --- .../examplecom/simple_service_pb_service.d.ts | 11 +++++++---- .../examplecom/simple_service_pb_service.js | 16 ++++++++++++++-- examples/generated/proto/orphan_pb_service.d.ts | 7 +++++-- examples/generated/proto/orphan_pb_service.js | 8 +++++++- src/service/grpcweb.ts | 17 +++++++++++++---- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts index 4c48a3e7..e925bea3 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts +++ b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts @@ -64,6 +64,9 @@ export type ServiceError = { message: string, code: number; metadata: grpc.Metad export type Status = { details: string, code: number; metadata: grpc.Metadata } export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean } +interface UnaryResponse { + cancel(): void; +} interface ResponseStream { cancel(): void; on(type: 'data', handler: (message: T) => void): ResponseStream; @@ -94,11 +97,11 @@ export class SimpleServiceClient { requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, metadata: grpc.Metadata, callback: (error: ServiceError|null, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void - ): void; + ): UnaryResponse; doUnary( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, callback: (error: ServiceError|null, responseMessage: proto_othercom_external_child_message_pb.ExternalChildMessage|null) => void - ): void; + ): UnaryResponse; doServerStream(requestMessage: proto_examplecom_simple_service_pb.StreamRequest, metadata?: grpc.Metadata): ResponseStream; doClientStream(metadata?: grpc.Metadata): RequestStream; doBidiStream(metadata?: grpc.Metadata): BidirectionalStream; @@ -106,10 +109,10 @@ export class SimpleServiceClient { requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, metadata: grpc.Metadata, callback: (error: ServiceError|null, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void - ): void; + ): UnaryResponse; delete( requestMessage: proto_examplecom_simple_service_pb.UnaryRequest, callback: (error: ServiceError|null, responseMessage: proto_examplecom_simple_service_pb.UnaryResponse|null) => void - ): void; + ): UnaryResponse; } diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.js b/examples/generated/proto/examplecom/simple_service_pb_service.js index bee0f019..4cfbff7a 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.js +++ b/examples/generated/proto/examplecom/simple_service_pb_service.js @@ -68,7 +68,7 @@ SimpleServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat if (arguments.length === 2) { callback = arguments[1]; } - grpc.unary(SimpleService.DoUnary, { + var client = grpc.unary(SimpleService.DoUnary, { request: requestMessage, host: this.serviceHost, metadata: metadata, @@ -87,6 +87,12 @@ SimpleServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat } } }); + return { + cancel: function () { + callback = null; + client.close(); + } + }; }; SimpleServiceClient.prototype.doServerStream = function doServerStream(requestMessage, metadata) { @@ -218,7 +224,7 @@ SimpleServiceClient.prototype.delete = function pb_delete(requestMessage, metada if (arguments.length === 2) { callback = arguments[1]; } - grpc.unary(SimpleService.Delete, { + var client = grpc.unary(SimpleService.Delete, { request: requestMessage, host: this.serviceHost, metadata: metadata, @@ -237,6 +243,12 @@ SimpleServiceClient.prototype.delete = function pb_delete(requestMessage, metada } } }); + return { + cancel: function () { + callback = null; + client.close(); + } + }; }; exports.SimpleServiceClient = SimpleServiceClient; diff --git a/examples/generated/proto/orphan_pb_service.d.ts b/examples/generated/proto/orphan_pb_service.d.ts index eb66da46..c18e0ee8 100644 --- a/examples/generated/proto/orphan_pb_service.d.ts +++ b/examples/generated/proto/orphan_pb_service.d.ts @@ -32,6 +32,9 @@ export type ServiceError = { message: string, code: number; metadata: grpc.Metad export type Status = { details: string, code: number; metadata: grpc.Metadata } export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean } +interface UnaryResponse { + cancel(): void; +} interface ResponseStream { cancel(): void; on(type: 'data', handler: (message: T) => void): ResponseStream; @@ -62,11 +65,11 @@ export class OrphanServiceClient { requestMessage: proto_orphan_pb.OrphanUnaryRequest, metadata: grpc.Metadata, callback: (error: ServiceError|null, responseMessage: proto_orphan_pb.OrphanMessage|null) => void - ): void; + ): UnaryResponse; doUnary( requestMessage: proto_orphan_pb.OrphanUnaryRequest, callback: (error: ServiceError|null, responseMessage: proto_orphan_pb.OrphanMessage|null) => void - ): void; + ): UnaryResponse; doStream(requestMessage: proto_orphan_pb.OrphanStreamRequest, metadata?: grpc.Metadata): ResponseStream; } diff --git a/examples/generated/proto/orphan_pb_service.js b/examples/generated/proto/orphan_pb_service.js index f2f6c77e..d9594ffb 100644 --- a/examples/generated/proto/orphan_pb_service.js +++ b/examples/generated/proto/orphan_pb_service.js @@ -39,7 +39,7 @@ OrphanServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat if (arguments.length === 2) { callback = arguments[1]; } - grpc.unary(OrphanService.DoUnary, { + var client = grpc.unary(OrphanService.DoUnary, { request: requestMessage, host: this.serviceHost, metadata: metadata, @@ -58,6 +58,12 @@ OrphanServiceClient.prototype.doUnary = function doUnary(requestMessage, metadat } } }); + return { + cancel: function () { + callback = null; + client.close(); + } + }; }; OrphanServiceClient.prototype.doStream = function doStream(requestMessage, metadata) { diff --git a/src/service/grpcweb.ts b/src/service/grpcweb.ts index baa49256..79f18dda 100644 --- a/src/service/grpcweb.ts +++ b/src/service/grpcweb.ts @@ -206,6 +206,9 @@ function generateTypescriptDefinition(fileDescriptor: FileDescriptorProto, expor printer.printLn(`export type Status = { details: string, code: number; metadata: grpc.Metadata }`); printer.printLn(`export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean }`); printer.printEmptyLn(); + printer.printLn("interface UnaryResponse {"); + printer.printIndentedLn("cancel(): void;"); + printer.printLn("}"); printer.printLn(`interface ResponseStream {`); printer.printIndentedLn(`cancel(): void;`); printer.printIndentedLn(`on(type: 'data', handler: (message: T) => void): ResponseStream;`); @@ -325,7 +328,7 @@ function printUnaryStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) .indent().printLn(`if (arguments.length === 2) {`) .indent().printLn(`callback = arguments[1];`) .dedent().printLn("}") - .printLn(`grpc.unary(${method.serviceName}.${method.nameAsPascalCase}, {`) + .printLn(`var client = grpc.unary(${method.serviceName}.${method.nameAsPascalCase}, {`) .indent().printLn(`request: requestMessage,`) .printLn(`host: this.serviceHost,`) .printLn(`metadata: metadata,`) @@ -344,7 +347,13 @@ function printUnaryStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) .dedent().printLn(`}`) .dedent().printLn(`}`) .dedent().printLn(`});`) - .dedent().printLn(`};`); + .printLn(`return {`) + .indent().printLn(`cancel: function () {`) + .indent().printLn(`callback = null;`) + .printLn(`client.close();`) + .dedent().printLn(`}`) + .dedent().printLn(`};`) + .dedent().printLn(`};`); } function printServerStreamStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) { @@ -510,11 +519,11 @@ function printUnaryStubMethodTypes(printer: CodePrinter, method: RPCMethodDescri .indent().printLn(`requestMessage: ${method.requestType},`) .printLn(`metadata: grpc.Metadata,`) .printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`) - .dedent().printLn(`): void;`) + .dedent().printLn(`): UnaryResponse;`) .printLn(`${method.nameAsCamelCase}(`) .indent().printLn(`requestMessage: ${method.requestType},`) .printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`) - .dedent().printLn(`): void;`); + .dedent().printLn(`): UnaryResponse;`); } function printServerStreamStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) { From 51f57eaaafa0434433c218e40a7649fe095a46ea Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 16 Oct 2018 15:56:15 -0400 Subject: [PATCH 15/15] Transport member of ServiceClientOptions should be optional (#125) * Transport member of ServiceClientOptions should be optional * Update examples --- .../generated/proto/examplecom/simple_service_pb_service.d.ts | 2 +- examples/generated/proto/orphan_pb_service.d.ts | 2 +- src/service/grpcweb.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts index e925bea3..cb4ac5cb 100644 --- a/examples/generated/proto/examplecom/simple_service_pb_service.d.ts +++ b/examples/generated/proto/examplecom/simple_service_pb_service.d.ts @@ -62,7 +62,7 @@ export class SimpleService { export type ServiceError = { message: string, code: number; metadata: grpc.Metadata } export type Status = { details: string, code: number; metadata: grpc.Metadata } -export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean } +export type ServiceClientOptions = { transport?: grpc.TransportConstructor; debug?: boolean } interface UnaryResponse { cancel(): void; diff --git a/examples/generated/proto/orphan_pb_service.d.ts b/examples/generated/proto/orphan_pb_service.d.ts index c18e0ee8..ab177ea5 100644 --- a/examples/generated/proto/orphan_pb_service.d.ts +++ b/examples/generated/proto/orphan_pb_service.d.ts @@ -30,7 +30,7 @@ export class OrphanService { export type ServiceError = { message: string, code: number; metadata: grpc.Metadata } export type Status = { details: string, code: number; metadata: grpc.Metadata } -export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean } +export type ServiceClientOptions = { transport?: grpc.TransportConstructor; debug?: boolean } interface UnaryResponse { cancel(): void; diff --git a/src/service/grpcweb.ts b/src/service/grpcweb.ts index 79f18dda..f3eba851 100644 --- a/src/service/grpcweb.ts +++ b/src/service/grpcweb.ts @@ -204,7 +204,7 @@ function generateTypescriptDefinition(fileDescriptor: FileDescriptorProto, expor printer.printLn(`export type ServiceError = { message: string, code: number; metadata: grpc.Metadata }`); printer.printLn(`export type Status = { details: string, code: number; metadata: grpc.Metadata }`); - printer.printLn(`export type ServiceClientOptions = { transport: grpc.TransportConstructor; debug?: boolean }`); + printer.printLn(`export type ServiceClientOptions = { transport?: grpc.TransportConstructor; debug?: boolean }`); printer.printEmptyLn(); printer.printLn("interface UnaryResponse {"); printer.printIndentedLn("cancel(): void;");