Skip to content

Improve type definitions to allow strict type-checking of ctx.call, actions definitions and events #822

@Telokis

Description

@Telokis

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.chat and v1.characters.
  • The service v1.chat defines two actions: get and list
  • The service v1.characters defines one action get
  • The action v1.chat.get takes a parameter containing toto: string and returns a boolean
  • The action v1.chat.list takes no parameter and returns a number
  • The action v1.characters.get takes a parameter containing id: string and returns a string

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...

Playground Link

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions