How to force typescript to interpret a parameter as a namespace rather than a class, when they have the same name - gRPC

576 views Asked by At

Sorry for the long title...

I am implementing a simple crud little system with gRPC and typescript, my problem is: The auto generated file creates a class and a type for each parameter in my protoFile, for instance: UserId parameter generates a class with getUserId etc, and a namespace with the type for UserId.

The problem is that when I try to use the method in my client, typescript expects a class as a parameter and not the type.

so instead of getUsersById({id: 1}, callback)... it asks me to do getUsersById(new UserId).

user.Proto:

syntax = "proto3";

package userServicePKG;

message User {
    int32 id = 1;
    string name = 2;
    int32 age = 3;
}

message UserId {
    int32 id = 1;
}

service UserService{
    rpc getUserById (UserId) returns (User);
}

UserServiceClientPb.ts (Protobuf generated) - Functions definition

methodInfogetUserById = new grpcWeb.AbstractClientBase.MethodInfo(
    User,
    (request: UserId) => {
      return request.serializeBinary();
    },
    User.deserializeBinary
  );

  getUserById(
    request: UserId,
    metadata: grpcWeb.Metadata | null): Promise<User>;

  getUserById(
    request: UserId,
    metadata: grpcWeb.Metadata | null,
    callback: (err: grpcWeb.Error,
               response: User) => void): grpcWeb.ClientReadableStream<User>;

  getUserById(
    request: UserId,
    metadata: grpcWeb.Metadata | null,
    callback?: (err: grpcWeb.Error,
               response: User) => void) {
    if (callback !== undefined) {
      return this.client_.rpcCall(
        new URL('/userServicePKG.UserService/getUserById', this.hostname_).toString(),
        request,
        metadata || {},
        this.methodInfogetUserById,
        callback);
    }
    return this.client_.unaryCall(
    this.hostname_ +
      '/userServicePKG.UserService/getUserById',
    request,
    metadata || {},
    this.methodInfogetUserById);
  }

user_pb.d.ts (Protobuf Generated) - Define types and classes:

export class UserId extends jspb.Message {
  getId(): number;
  setId(value: number): UserId;

  serializeBinary(): Uint8Array;
  toObject(includeInstance?: boolean): UserId.AsObject;
  static toObject(includeInstance: boolean, msg: UserId): UserId.AsObject;
  static serializeBinaryToWriter(message: UserId, writer: jspb.BinaryWriter): void;
  static deserializeBinary(bytes: Uint8Array): UserId;
  static deserializeBinaryFromReader(message: UserId, reader: jspb.BinaryReader): UserId;
}

export namespace UserId {
  export type AsObject = {
    id: number,
  }
}

Client.Vue:

const client = new UserServiceClient('http://localhost:5001', null, null);

let userId = { id:1 };
client.getUserById(userId, function (error: grpcWeb.Error, response: any) {
     //do something
});

the parameter userId fires the following error:

Argument of type '{ id: number; }' is not assignable to parameter of type 'UserId'. Type '{ id: number; }' is missing the following properties from type 'UserId': getId, setId, serializeBinary, toObject, and 8 more.Vetur(2345)

It looks to me that typescript is inferring that getUserById first parameter is of the type Class UserId instead of the type coming from the namespace UserId.

Is there something I can do about it? Since it was autogenerated, I feel like it should interpret correctly? Am I messing up something else? I am new to gRPC so I may be doing something wrong.

Thanks in advance!

1

There are 1 answers

1
Timo Stamm On BEST ANSWER

The message UserId is generated as a JavaScript class. You have to provide an instance of this class, there isn't really any alternative, at least not with the code generated by protoc-gen-grpc-web.

There are some alternative code generators for TypeScript-based that do not require you to use classes. ts-proto as well as protobuf-ts use simple interfaces instead of classes to represent messages. (I am the author of protobuf-ts).

For example, protobuf-ts generates code from your user.proto that can be used like this:

// gRPC-web provided by @protobuf-ts/grpcweb-transport
const transport = new GrpcWebFetchTransport("http://localhost:5001");

const client = new UserServiceClient(transport);

const {response} = await client.getUserById({id: 123});

console.log(
    response.id,
    response.name,
    response.age
);

I think ts-proto also got grpc-web support some time ago. Maybe give one of those a try and see if you like the generated code better.