-
-
Notifications
You must be signed in to change notification settings - Fork 598
Description
Is your feature request related to a problem? Please describe.
The current type definitions does not provide any type-safety regarding ctx.call, ctx.emit, actions definitions in service and events handlers in services.
Describe the solution you'd like
We could use several meta-types and a user-defined mapping allowing us to properly type everything.
In particular, I was thinking about the following for user-defined mappings related to actions:
type ServiceActions = {
"v1.chat": {
get: (arg: { toto: string }) => boolean;
list: () => number;
};
"v1.characters": {
get: (arg: { id: string }) => string;
};
};The meaning of this type is:
- We have two services available:
v1.chatandv1.characters. - The service
v1.chatdefines two actions:getandlist - The service
v1.charactersdefines one actionget - The action
v1.chat.gettakes a parameter containingtoto: stringand returns aboolean - The action
v1.chat.listtakes no parameter and returns anumber - The action
v1.characters.gettakes a parameter containingid: stringand returns astring
Given this mapping, we have every information we need.
I thought about using it the following way (take a look at the inheritance):
class ChatService extends Service<ServiceActions, "v1.chat"> {
constructor(broker: ServiceBroker) {
super(broker);
this.parseServiceSchema({
name: "chat",
version: "v1",
actions: {
async get(ctx) {
const str = await ctx.call("v1.characters.get"); // Type error: `id: string` was expected as parameter
return ctx.params.toto; // Triggers a type error because it should return a boolean
},
list: {
handler(ctx) {
return "plop"; // Triggers a type error because it should return a number
},
},
},
});
}
}To properly type events, we also rely on a mapping of the following form:
type ServiceEvents = {
"event1": { id: string };
};Meaning "There is one event event1 and its payload is an object containing an id of type string."
We don't need anything else in order to have strongly-types events. This mapping would need to be passed as a third template parameter to the Service class, though.
Describe alternatives you've considered
I haven't considered any alternative, this is the only proper solution I could think of that would cover all my requirements.
Additional context
A potential issue with the implemention I think about is that it relies on TypeScript 4.1 (Specifically this issue). This is used to generate v1.characters.get from the keys of the mapping.
This feature is a lifesaver since it lets us stay DRY (In the action definition we don't use the service name but we use it in the action call)
I've already started working on the implementation because I think it's mandatory when using such a library in TypeScript.
I currently have the action implementation strongly typed and am working on ctx.call.
I can show the code and explain everything if it is relevant.
The following code was the proof of concept used to find the way to go, if someone is curious:
interface Actions {
"v1.auth": {
get: (toto: string) => boolean;
list: () => number;
};
"v1.characters": {
get: (id: string) => string;
}
}
type Func = (...arg...
Just to clarify: I intend to implement this. I just thought I could open an issue to discuss the used interface because it might be useful for people. It could become a PR if the solution I implement is satisfying for everyone.
And I stumbled on this "problem" while tinkering: #467 (comment). It's not a problem without this feature but gets in the way of the implementation I'm working on.