-
Notifications
You must be signed in to change notification settings - Fork 58
Argument of type 'Handler' is not assignable to parameter of type 'UntypedServiceImplementation' #79
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yes, it's a known issue. Let me have a look what I can do to solve | improve it. |
New version v5.1.0 has been published. Closing. |
@agreatfool can you link the code changes to this issue? |
Thanks. Great to see this fixed. It looks like we now have Type parity across the popular tools:
I'll update my example in https://github.com/badsyntax/grpc-js-types/tree/master/examples/grpc_tools_node_protoc_ts#issues-with-this-approach to show this latest change. |
I'm not sure this is fixed. After updating to 5.1.0, I started getting TS compilation errors due to this change. VS Code expects the following to be in my implementation: class MyServerImpl implements IMyServiceServer {
[name: string]: grpc.UntypedHandleCall; // <== This line is expected...
// But if I have properties / methods that are not RPC-handling like...
data: Foo = ...;
someOtherFunction() {...}
} I get the following compiler error: Property 'data' of type 'Foo' is not assignable to string index type 'HandleCall<any, any>'.ts(2411) |
@codedread I did't expect the case of private attributes of the implementation class. Seems this breaking change affected a lot. I will have a check with it, maybe revert this. Fixing one issue and introduce another is not good. Sorry for the inconvenience. |
New version I recommend the 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); Read more here. |
I think this is the right approach. While no-one likes |
@badsyntax That's pretty cool. Thanks for the sharing. |
Sadly, not supporting the class syntax means at least two not-great things:
Yes, I can work around this but it is not great. My current TypeScript: import { Logger} from 'logger';
class MyService extends IMyService {
logger: Logger;
constructor() {
this.logger = new Logger();
}
// rest of GRPC things here...
} In my test, I want to mock out Logger as a dependency: import { * as loggerModule } from 'logger';
describe('', () => {
const myService: MyService;
beforeEach(() => {
importMock.mockClassInPlace(commonModule, 'Logger');
myService = new MyService(); // Each test gets a new instance of a service...
});
// All my tests here that don't have to worry about the Logger dependency...
} |
Currently it seems this is the only possible way to do even from official comments : grpc/grpc-node#1474 (comment). For class style, maybe we can make something like this (idea, not tested): class ServiceWrapper {
// class attributes & other stuff
// ...
public svcImpl: 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 impl = new ServiceWrapper().svcImpl;
const server = new grpc.Server();
server.addService(BookServiceService, impl); |
Hi everyone, I hit the same issue with the typings for export interface ITypedGreeterServer {
sayHello: grpc.handleUnaryCall<protos_greeter_pb.HelloRequest, protos_greeter_pb.HelloReply>;
}
export interface IGreeterServer extends ITypedGreeterServer, grpc.UntypedServiceImplementation {
} This would allow Backwards-compatible type-safe addService(service: ServiceDefinition, implementation: UntypedServiceImplementation): void;
addService<TypedServiceImplementation extends Record<any,any>>(service: ServiceDefinition, implementation: TypedServiceImplementation): void; Then you can use it like this: class GreeterServiceImpl implements services.ITypedGreeterServer {
...
}
const impl = new GreeterServiceImpl();
server.addService<services.ITypedGreeterServer>(services.GreeterService, impl); and everything else should just keep working. Alternatively, if you want to get 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>>; Which gets you: type ITypedGreeterServer = KnownOnly<services.IGreeterServer>; |
@jahewson is any chance you have a more complete example you can share with the generic magic you're suggesting? Here's an attempt of mine but it's failing: 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>>;
type ITypedArticleServer = KnownOnly<IArticleServerServer>;
class ArticleServer implements ITypedArticleServer {
key: string
secret: string
constructor(key: string, secret: string) {
this.key = key
this.secret = secret
}
async getArticle(_: ServerUnaryCall<Empty, Empty>, callback: sendUnaryData<Empty>): Promise<void> {
const files = await fleekStorage.listFiles({
apiKey: this.key,
apiSecret: this.secret,
getOptions: [
'bucket',
'key',
'hash',
'publicUrl'
],
})
console.log(files)
callback(null, new Empty())
}
}
const server = async (yargs: yargs.Arguments) => {
const apiKey = String(yargs['key']);
const apiSecret = String(yargs['secret']);
const port = yargs['port']
const server = new grpc.Server();
server.addService(ArticleServerService, new ArticleServer(apiKey, apiSecret)); // <--------------- this errors out!!!
server.bindAsync(`localhost:${port}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err) {
throw err;
}
console.log(`Listening on ${port}`);
server.start();
});
} And here's the error I get on the EDIT: ah, looks like I need to add a typed override to the EDIT 2: ah, looks like I figured it out. I created a class which extends class TypedServerOverride extends grpc.Server {
addServiceTyped<TypedServiceImplementation extends Record<any,any>>(service: ServiceDefinition, implementation: TypedServiceImplementation): void {
this.addService(service, implementation)
}
} And then when created the server object I do it like so: const server = new TypedServerOverride();
server.addServiceTyped<ITypedArticleServer>(ArticleServerService, new ArticleServer(apiKey, apiSecret));
server.bindAsync(`localhost:${port}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err) {
throw err;
}
console.log(`Listening on ${port}`);
server.start();
}); |
Add new docs explaining how to create GRPC services with classes based on some magic generics from agreatfool#79 (comment)
Upgraded to the latest and started getting the error message above when using the addService call. Noticed that in your example you are ignoring the compilation issue with the ts-ignore directive, is this a known issue?
grpc_tools_node_protoc_ts/examples/src/grpcjs/server.ts
Line 75 in b85f144
The text was updated successfully, but these errors were encountered: