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

Fix missing TypeScript return type for serverStreaming calls. #1167

Merged

Conversation

lukasmoellerch
Copy link
Contributor

@lukasmoellerch lukasmoellerch commented Nov 11, 2021

Should fix #950

In the generated typescript code this will ensure that the generic parameters will be inferred from the values passed to the constructor. This should only make a difference when the import_style setting is set to typescript. Afterwards in the following code snippet the the response parameter will correctly infer to ServerStreamingEchoResponse, whereas previously it would be typed as unknown.

import * as grpcWeb from 'grpc-web';

import {EchoRequest, EchoResponse,
        ServerStreamingEchoRequest,
        ServerStreamingEchoResponse} from './generated/echo_pb';
import {EchoServiceClient} from './generated/EchoServiceClientPb';

const echoService = new EchoServiceClient('http://localhost:8080', null, null);

let call2 =
  echoService.serverStreamingEcho(new ServerStreamingEchoRequest(), {});

call2
  .on('data', (response) => {
  })
  .on('error', (error: grpcWeb.RpcError) => {
  });

I am not entirely sure where tests for the typescript mode are located, I was only able to finde the tsc_tests, but they seem to be using the dts generator.

In the generated typescript code this will ensure that the generic
parameters will be infered from the values passed to the constructor
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Nov 11, 2021

CLA Signed

The committers are authorized under a signed CLA.

@lukasmoellerch lukasmoellerch changed the title Add strongly types to MethodDescriptor constructor Add types to MethodDescriptor constructor Nov 11, 2021
Copy link
Collaborator

@sampajano sampajano left a comment

Choose a reason for hiding this comment

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

@lukasmoellerch Thanks so much for the fix! This is pretty smart :D Should be a great improvement! 😃

Just a few thoughts:

  1. I think part of the problem is that the generated client stub (EchoServiceClientPb.ts in your example) doesn't specify return type. I wonder if that can be fixed as well?

    These are the relevant lines if i read correctly:

    printer->Print(vars, "$js_method_name$(\n");
    printer->Indent();
    printer->Print(vars,
    "request: $input_type$,\n"
    "metadata?: grpcWeb.Metadata) {\n");

  2. I wonder if you could just remove type specifications in our TS example to help verify that type reference indeed works?

    this.stream.on('data', (response: ServerStreamingEchoResponse) => {
    EchoApp.addRightMessage(response.getMessage());
    });
    this.stream.on('status', (status: grpcWeb.Status) => {
    if (status.metadata) {
    console.log('Received metadata');
    console.log(status.metadata);
    }
    });
    this.stream.on('error', (err: grpcWeb.RpcError) => {
    EchoApp.addRightMessage(
    'Error code: ' + err.code + ' "' + err.message + '"');
    });

    FYI, you have to comment option 1 and uncomment option 2 to get Typescript input:

    // Option 2: import_style=typescript
    // import {EchoServiceClient} from './EchoServiceClientPb';

Thanks! :)

Comment on lines 76 to 77
requestSerializeFn: (request: REQ) => Uint8Array,
responseDeserializeFn: (bytes: Uint8Array) => RESP);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you make the return type to match the Closure type definition here? The fact it's currently using Uint8Arry is implementation detail and should probably be omitted.

* @param {function(REQUEST): ?} requestSerializeFn

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 am not quite sure why it would make sense to have these typed as wither unknown or any - if we allow more values here consumers will be allowed to create MethodDescriptor instances which break internal invariants, e.g. I could create a MethodDescriptor with a serialize function which returns a number and typescript won't complain, but during runtime it will break. In my opinion MethodDescriptor is part of the public interface and thus the constructor should only allow valid values to be passed to the constructor. Nevertheless I can totally understand if you want it to return an interface / unknown instead, maybe it's just a matter of personal preference.

Copy link
Collaborator

@sampajano sampajano Nov 19, 2021

Choose a reason for hiding this comment

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

if we allow more values here consumers will be allowed to create MethodDescriptor instances which break internal invariants

I get your sentiment in general.. Although because MethodDescriptor code is generated rather than specified by callers, this probably doesn't make a practical difference..

Nevertheless I can totally understand if you want it to return an interface / unknown instead, maybe it's just a matter of personal preference.

The main reason i want to keep this in sync with the Closure type annotation is that internally (Google) we're referring to the Closure type as source of truth, and only on Github we're also adding the TS type info. And because the internal types are subject to change, I don't want us to commit to stronger types in TS than we do in JS. If that makes sense :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok sure, thanks for the explanation. I reverted the types of requestSerializeFn and responseDeserializeFn to any.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks a lot :)

Comment on lines 88 to 89
getRequestSerializeFn(): (request: REQ) => Uint8Array;
getResponseDeserializeFn(): (bytes: Uint8Array) => RESP;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above :)

@lukasmoellerch
Copy link
Contributor Author

Thanks for the quick response! Regarding your first point:

I think part of the problem is that the generated client stub (EchoServiceClientPb.ts in your example) doesn't specify return type. I wonder if that can be fixed as well?

We could add a return type declaration here, but the inferred return type is already correct I think: Because serverStreaming returns a ClientReadableStream<RESP> as per its declaration in index.d.ts the inferred return type will also be ClientReadableStream with the generic parameter being inferred from the method descriptor. We could still add a direct annotation there, but I don't think that it will change the type-checking in any way.

I wonder if you could just remove type specifications in our TS example to help verify that type reference indeed works?

Yes, I think we should be able to remove those. I'll quickly try that, thanks for the hint.

@lukasmoellerch
Copy link
Contributor Author

Yes, I think we should be able to remove those. I'll quickly try that, thanks for the hint.

Ok, I did some quick testing: After convincing typescript to use the new type declarations with the following tsconfig file:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "allowJs": true,
    "outDir": "./dist",
    "types": ["node"],
    "baseUrl": "./",
    "paths": {
      "grpc-web": ["../../../../../../packages/grpc-web/"]
    }
  },

  "include": [
    "client.ts",
    "echo_pb.js",
    "echo_pb.d.ts",
    "echo_grpc_web_pb.js",
    "echo_grpc_web_pb.d.ts",
    "EchoServiceClientPb.ts"
  ],
  "exclude": ["node_modules"]
}

I indeed get the correct parameter type inferred and the parameter type inferred correctly:

Screen Shot 2021-11-13 at 12 57 07

and the serverStreamingEcho method has the correct type as well:

EchoServiceClient.serverStreamingEcho(request: ServerStreamingEchoRequest, metadata?: grpcWeb.Metadata | undefined): grpcWeb.ClientReadableStream<ServerStreamingEchoResponse>

whereas previously I got the following error:

Type 'ClientReadableStream<unknown>' is not assignable to type 'ClientReadableStream<ServerStreamingEchoResponse>'.
  Type 'unknown' is not assignable to type 'ServerStreamingEchoResponse'

in the line where this.stream is assigned.

Copy link
Collaborator

@sampajano sampajano left a comment

Choose a reason for hiding this comment

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

@lukasmoellerch Thanks so much for the change again! Mostly LGTM after resolving the open comments :)

We could add a return type declaration here, but the inferred return type is already correct I think: Because serverStreaming returns a ClientReadableStream<RESP> as per its declaration in index.d.ts the inferred return type will also be ClientReadableStream with the generic parameter being inferred from the method descriptor. We could still add a direct annotation there, but I don't think that it will change the type-checking in any way.

Yup agreed that inference is already working after your change.. I just thought also adding it to the generated TS file will add even a stronger guarantee of type info (e.g. inference might be accidentally broken).

But I'm fine if we we leave this as is and maybe do a follow-up change later :)

I indeed get the correct parameter type inferred and the parameter type inferred correctly:

Fantastic! Thanks for verifying! :)

Comment on lines 76 to 77
requestSerializeFn: (request: REQ) => Uint8Array,
responseDeserializeFn: (bytes: Uint8Array) => RESP);
Copy link
Collaborator

@sampajano sampajano Nov 19, 2021

Choose a reason for hiding this comment

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

if we allow more values here consumers will be allowed to create MethodDescriptor instances which break internal invariants

I get your sentiment in general.. Although because MethodDescriptor code is generated rather than specified by callers, this probably doesn't make a practical difference..

Nevertheless I can totally understand if you want it to return an interface / unknown instead, maybe it's just a matter of personal preference.

The main reason i want to keep this in sync with the Closure type annotation is that internally (Google) we're referring to the Closure type as source of truth, and only on Github we're also adding the TS type info. And because the internal types are subject to change, I don't want us to commit to stronger types in TS than we do in JS. If that makes sense :)

- Adds attribute type declaratiosn to method descriptor
- Adds return type declaration to server streaming call
…ellerch/grpc-web into ts-method-descriptor-constructor
@lukasmoellerch
Copy link
Contributor Author

@lukasmoellerch Thanks so much for the change again! Mostly LGTM after resolving the open comments :)

We could add a return type declaration here, but the inferred return type is already correct I think: Because serverStreaming returns a ClientReadableStream<RESP> as per its declaration in index.d.ts the inferred return type will also be ClientReadableStream with the generic parameter being inferred from the method descriptor. We could still add a direct annotation there, but I don't think that it will change the type-checking in any way.

Yup agreed that inference is already working after your change.. I just thought also adding it to the generated TS file will add even a stronger guarantee of type info (e.g. inference might be accidentally broken).

But I'm fine if we we leave this as is and maybe do a follow-up change later :)

Sure, I modified the code generator, it should now generate the type annotations for server streaming methods and also for the generic params of the method descriptors. I hope the latter change is okay.

@sampajano
Copy link
Collaborator

sampajano commented Nov 19, 2021

@lukasmoellerch Thanks so much for the change again! Mostly LGTM after resolving the open comments :)

We could add a return type declaration here, but the inferred return type is already correct I think: Because serverStreaming returns a ClientReadableStream<RESP> as per its declaration in index.d.ts the inferred return type will also be ClientReadableStream with the generic parameter being inferred from the method descriptor. We could still add a direct annotation there, but I don't think that it will change the type-checking in any way.

Yup agreed that inference is already working after your change.. I just thought also adding it to the generated TS file will add even a stronger guarantee of type info (e.g. inference might be accidentally broken).
But I'm fine if we we leave this as is and maybe do a follow-up change later :)

Sure, I modified the code generator, it should now generate the type annotations for server streaming methods and also for the generic params of the method descriptors. I hope the latter change is okay.

Thanks a lot for the change! My personal preference is to omit the method descriptor types just because 1) users should rarely need to reference them directly, 2) this is a case where type inference becomes very clear after your change (whereas for the streaming endpoint it's more roundabout) :)

But since this is generated code so a little extra verbosity maybe doesn't hurt.. I'll leave it to you :)

Copy link
Collaborator

@sampajano sampajano left a comment

Choose a reason for hiding this comment

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

@lukasmoellerch Thanks again for this great change! And your patience during code review! LGTM now! :)

@lukasmoellerch
Copy link
Contributor Author

But since this is generated code so a little extra verbosity maybe doesn't hurt.. I'll leave it to you :)

On second thought I have to agree: I think keeping the generated code clean is probably preferable here. I reverted that change.

@sampajano
Copy link
Collaborator

sampajano commented Nov 19, 2021

On second thought I have to agree: I think keeping the generated code clean is probably preferable here. I reverted that change.

Oh cool sgtm! Thanks again for your contribution and patience through the reviews! :)

I'll cut a 1.3.1 release soon. I'm excited for this improvement! :D

(btw i've modified the name of the PR to better reflect the actual function of it :))

@sampajano sampajano changed the title Add types to MethodDescriptor constructor Fix missing TypeScript return type for serverStreaming calls. Nov 19, 2021
@sampajano sampajano merged commit 6b1d1e9 into grpc:master Nov 19, 2021
@chrisdrobison
Copy link

@sampajano Do you plan you cut a new release so this can be pushed out?

@sampajano
Copy link
Collaborator

Yes! I'm sorry about the delay! I'll aim to do this in the next week! 😃

@sampajano
Copy link
Collaborator

@chrisdrobison This should now be in release 1.3.1. thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Missing TypeScript return type for serverStreaming
5 participants