Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TypeScript typings to gRPC native core package #52

Merged
merged 11 commits into from
Oct 10, 2017
Merged

Add TypeScript typings to gRPC native core package #52

merged 11 commits into from
Oct 10, 2017

Conversation

Crevil
Copy link
Contributor

@Crevil Crevil commented Sep 29, 2017

Following @murgatroid99's request I've moved the PR grpc/grpc#12755 to this repo instead.

From the original PR:

This is a follow up of grpc/grpc#11020. I've accidentally deleted the old gRPC fork I had as I had totally forgotten this PR.

This is a remake with all the latest API changes and the comments from the former PR added.
Further more, I've created a test repo to verify that the type comply with the documented API. It is located here for now: https://github.com/lunarway/grpc-ts-test
If it can fit into the structure of the repo, we could incorporate it to make sure the type and tests stay up to date? Let me know if there is anything I can do in that case.

From the initial PR:

This PR closes grpc/grpc#8233
It adds the most basic typings for TypeScript of the public API of grpc for Node.js.

All details are not defined in this, as I'm no expert in reading the native parts of the module. I've used these definitions for a while and trusts the API. Let me know if anything needs to be corrected or otherwise modified.

All comments etc. are from the existing documentations.

Example usage:

import * as grpc from 'grpc';

const server = new grpc.Server();
server.bind('0.0.0.0:7200', grpc.ServerCredentials.createInsecure());
server.start();

Copy link
Member

@murgatroid99 murgatroid99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the APIs that intersect, this needs to be consistent with the type definitions in packages/grpc-js-core. Can you look there and see if anything there is inconsistent with anything you wrote here?

* Callback function passed to server handlers that handle methods with
* unary responses.
*/
type sendUnaryData = (error: ServiceError | null, value: any, trailer?: Metadata, flags?: number) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ��null string appears a few times in this file, and it looks like an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sure looks weird... Let me get to that. And I will give the API a look in grpc-js-core as well to make sure we are in line.

*/
export interface GrpcObject {
[namespace: string]: {
[service: string]: typeof Client,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't exactly one namespace level; there can actually be 0 or more namespaces, and there are also message classes in there, so it should really be something like

import { Message } from "protobufjs";
export interface GrpcObject {
  [name: string]: GrpcObject | typeof Client | Message,
}

I'd prefer not to mention the message classes, but without them the type definition wouldn't accurately represent the actual API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I was a bit confused about this part when looking at the docs. I'll clear it up.

* of the grpc.logVerbosity map.
* @param verbosity The minimum severity to log
*/
export function setLogVerbosity(verbosity: number): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logVerbosity enum is exported below. Should the argument to this function use that enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'r right. This could very well be the enum.

* @param implementation Map of method names to method implementation
* for the provided service.
*/
addProtoService(service: Service, implementation: { [name: string]: handleCall }): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's not currently documented this way, but the first argument to this function can actually be a Service (from Protobuf.js), or a ServiceDefinition object (defined below), or a Service object from Protobuf.js 6 (though it will be difficult to get that type definition here). This is important because the Client#service instance variable is a valid argument to this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

* @param creds Server credential object to be used for SSL. Pass an
* insecure credentials object for an insecure port.
*/
bind(port: string, creds: ServerCredentials): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not actually return void; it returns a number that is equal to the bound port, or 0 if the operation failed. The lack of documentation of this fact is an error.

* @param call The call object associated with the request
* @param metadata The request metadata from the client
*/
constructor(call: Call, metadata: Metadata);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this isn't necessary, I would prefer to omit it. This constructor is not explicitly exposed in the API. In addition, the Call type is defined elsewhere as the union of the client call object types, but that is not the correct type for this argument. The argument to this constructor is another type that is also not exposed in the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove them.

* @param serialize Serialization function for requests
* @param deserialize Deserialization function for requests
*/
constructor(call: any, metadata: Metadata, serialize: serialize, deserialize: deserialize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, this constructor should not be in the type definition

/**
* The error code, a key of `grpc.status`
*/
code: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of code should be the status enum defined below.

* @param googleCredential The Google credential object to use
* @return The resulting credentials object
*/
createFromGoogleCredential(googleCredential: GoogleCredential): CallCredentials;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that GoogleCredential is defined in this file, which is not surprising because gRPC does not explicitly depend on the library that that type comes from. That library is google-auth-library. I think it would be OK to depend just on the type definitions for that library, if such a module exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a copy of the types into this file, see line 864.
The lib is written in TypeScript so it ships with it's own typings (https://github.com/google/google-auth-library-nodejs/blob/master/package.json#L10)
How would you prefer this? I dep to the lib or a copy of the type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that copy now, and I think that's the right way to handle this. But that's not the right type. I think what I called GoogleCredentials actually corresponds to what's listed as OAuth2Client in that library. The important part of that interface, from the point of view of how this library uses it, is the getRequestMetadata method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's my mistake then. I'll change it to the right one.

I've put som questions into the other comments you left, but I think they've been hidden by the other changes. Would you look into them as well?

* An EventEmitter. Used for unary calls.
* @param call The call object associated with the request
*/
constructor(call: any);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with the server-side call classes, I think these constructors should be omitted from the type definition.

*/
type handleClientStreamingCall = (call: ServerReadableStream, callback: sendUnaryData) => void;

export class ServerReadableStream {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the actual API, this class inherits from a Readable stream in object mode. Could the fact that those APIs are not in the type definitions cause problems for users? This applies to all of the streaming call types on both the client and server side, as well as the UnaryClientCall type, which inherits from EventEmitter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this one. I think we need to explicitly inherit from those types as well otherwise something like this would cause an error.

let c: ServerUnaryCall;
c.on("data", () => {});

To make this possible we need the Node.js typings. Should I include them as a dev dependency?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with depending on the Node.js typings. Wouldn't we have to include them as a regular dependency, if this index.d.ts we're distributing in the release references it?

Also, it's not as simple as just inheriting from those types. Those types are explicitly written with the assumption that the stream is a byte stream, and it takes some intermediate type redefinitions to make them valid object stream types. You can see what I had to do for this in the other package here: https://github.com/grpc/grpc-node/blob/master/packages/grpc-js-core/src/object-stream.ts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't have to do as much as that, because you don't necessarily need strongly typed object streams that restrict inputs and outputs to the specific request and response types. But you at least need equivalents of the intermediate types to allow any in the inputs to write operations and the outputs from read operations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into the definitions and see what I can do with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, I've looked into this. The tests allow for your requested API, so I think we are good for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the changes you made here quite work. As I mentioned, the builtin stream types are written for byte streams. They handle buffers and strings, not arbitrary objects. You need to override the method definitions, like I did in that object stream TypeScript file.

Also, it looks like you didn't change all of the client stream types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When looking into the Node.js typings it seems to allow the any type for both read results (stream.Readable) and write messages (stream.Writeable).

See this test file where it's possible to write an object: https://github.com/lunarway/grpc-ts-test/blob/master/index.ts#L42

Let me know if I've misunderstood something here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the readable side, you can see that the data event restricts the values to Buffer | string: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/index.d.ts#L5067. The Writable type is actually fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first overload of on allows for the any type when reading data events.

readableStream.on("data", (chunk: object) => {});

I've made a test case in this commit: https://github.com/lunarway/grpc-ts-test/commit/23a74d585e2701a993f0b874ad017152cbc41083

* @param credentials Credentials to use to connect to the server
* @param {Object} options Options to apply to channel creation
*/
constructor(address: string, credentials: ChannelCredentials, options?: object)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I creaed typings for the options here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I'll merge them into this file. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good to know; I would prefer not to include those in this PR. There's some complexity with handling the case where new arguments are added, and I think we'll be able to handle that better in the future after some other changes we're planning to make.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger. We leave them out for now then.

@Crevil
Copy link
Contributor Author

Crevil commented Oct 6, 2017

I've identified the following differences from grpc-js-core package.

  • The method Client#getChannel(): Channel L773 is not exposed in js-core.
  • Functions not found in js-core

@murgatroid99 murgatroid99 merged commit 0182df2 into grpc:master Oct 10, 2017
@murgatroid99
Copy link
Member

Thank you for your contribution. The typings file will be in grpc@1.7, which will be published soon.

@ksaldana1
Copy link

ksaldana1 commented Oct 10, 2017

Thanks a bunch @Crevil for taking the initiative on this and getting it done! I know I've been eagerly waiting for this, so I'm sure others are too. Thanks also to @murgatroid99 for the code reviews and @connor4312 for providing his typings file to build from.

@Crevil
Copy link
Contributor Author

Crevil commented Oct 11, 2017

Thank you for the great review @murgatroid99 .
Let me know if there is anything I can help you with in the future.

@chrisalexander55
Copy link

Is this available in the latest build? I am unable to find a type definition file in node_modules/grpc directory. Thanks.

@murgatroid99
Copy link
Member

Today (hopefully) we will be publishing a prerelease package for the next version, which includes these changes.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typescript typings for node package
6 participants