Issue: Issue#79.
This one is fixed in version v5.1.0
, and there should be some document to explain more.
If you are using this tool of version v5.0.1
, you may have already noticed a signature issue here: examples/src/grpcjs/server.ts#L74-L75. Note the // @ts-ignore
:
// @ts-ignore
server.addService(BookServiceService, new ServerImpl());
The signature of implementation class looks like:
export const BookServiceService: IBookServiceService;
export interface IBookServiceServer {
getBook: grpc.handleUnaryCall<book_pb.GetBookRequest, book_pb.Book>;
getBooksViaAuthor: grpc.handleServerStreamingCall<book_pb.GetBookViaAuthor, book_pb.Book>;
getGreatestBook: handleClientStreamingCall<book_pb.GetBookRequest, book_pb.Book>;
getBooks: grpc.handleBidiStreamingCall<book_pb.GetBookRequest, book_pb.Book>;
}
class ServerImpl implements IBookServiceServer {
}
The signature of addService
is:
export class Server {
addService(
service: ServiceDefinition,
implementation: UntypedServiceImplementation
): void {}
}
As you can see the type of implementation need to be UntypedServiceImplementation
. So it's inconsistent.
The way of fixing is to define IBookServiceServer to extends UntypedServiceImplementation
.
export interface IBookServiceServer extends grpc.UntypedServiceImplementation {
}
Though this fixing solved the issue of signature issue here (the original one):
// @ts-ignore
server.addService(BookServiceService, new ServerImpl());
It brings the new issue.
Because UntypedServiceImplementation
is defined like:
export interface UntypedServiceImplementation {
[name: string]: UntypedHandleCall;
}
This means all the attributes inside the UntypedServiceImplementation
(and any derived implementation, like class ServerImpl implements IBookServiceServer extends grpc.UntypedServiceImplementation
) have to be type of UntypedHandleCall
.
So when you define a class to implement IBookServiceServer
, you have to add a line of code, like:
class Impl implements IBookServiceServer {
[name: string]: grpc.UntypedHandleCall;
}
Otherwise, tsc would throw errors: Typescript: Index signature is missing in type.
According to this breaking change, there could be two styles of server side implementation. One is Object style
, the other is Class style
.
const Impl: IBookServiceServer = {
getBook: (call: grpc.ServerUnaryCall<GetBookRequest, Book>, callback: sendUnaryData<Book>): void => {},
getBooks: (call: grpc.ServerDuplexStream<GetBookRequest, Book>): void => {},
getBooksViaAuthor: (call: grpc.ServerWritableStream<GetBookViaAuthor, Book>): void => {},
getGreatestBook: (call: grpc.ServerReadableStream<GetBookRequest, Book>, callback: sendUnaryData<Book>): void => {},
};
const server = new grpc.Server();
server.addService(BookServiceService, Impl);
class Impl implements IBookServiceServer {
[name: string]: grpc.UntypedHandleCall;
public getBook(call: grpc.ServerUnaryCall<GetBookRequest, Book>, callback: sendUnaryData<Book>): void {}
public getBooks(call: grpc.ServerDuplexStream<GetBookRequest, Book>) {}
public getBooksViaAuthor(call: grpc.ServerWritableStream<GetBookViaAuthor, Book>) {}
public getGreatestBook(call: grpc.ServerReadableStream<GetBookRequest, Book>, callback: sendUnaryData<Book>) {}
}
const server = new grpc.Server();
server.addService(BookServiceService, new Impl());
Only those already defined in the IBookServiceServer
can be existing in this Impl
class, and [name: string]: grpc.UntypedHandleCall;
is required for Class style.
Class style with magic generics
The following is how you can break the restriction above, to add attributes to a class style implementation of a server:
// First we need to set up some magic generics inspired by https://github.com/agreatfool/grpc_tools_node_protoc_ts/issues/79#issuecomment-770173789
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
type KnownOnly<T extends Record<any, any>> = Pick<T, KnownKeys<T>>;
// Next we declare a new type using the above generics:
type ITypedBookServer = KnownOnly<IBookServiceServer>;
// Now we declare the class
class Impl implements ITypedBookServer {
attr: string;
constructor(attr: string) {
this.attr = attr;
}
public getBook(call: grpc.ServerUnaryCall<GetBookRequest, Book>, callback: sendUnaryData<Book>): void {}
public getBooks(call: grpc.ServerDuplexStream<GetBookRequest, Book>) {}
public getBooksViaAuthor(call: grpc.ServerWritableStream<GetBookViaAuthor, Book>) {}
public getGreatestBook(call: grpc.ServerReadableStream<GetBookRequest, Book>, callback: sendUnaryData<Book>) {}
}
// now we need to initialize the above Impl. First we need to extend grpc.Server
class TypedServerOverride extends grpc.Server {
addTypedService<TypedServiceImplementation extends Record<any,any>>(service: ServiceDefinition, implementation: TypedServiceImplementation): void {
this.addService(service, implementation);
}
}
// now perform the actual initialization
const server = new TypedServerOverride();
server.addTypedService<ITypedBookServer>(BookServiceService, new Impl("hello world"));
Runnable example could be found here: