Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeScript] define events interface #163

Open
alonronin opened this issue May 17, 2022 · 3 comments
Open

[TypeScript] define events interface #163

alonronin opened this issue May 17, 2022 · 3 comments
Labels

Comments

@alonronin
Copy link

Hi,

I want to define an events interface like so:

const events = {
  test: (payload) => {
    console.log(payload);
  },
  another: (payload) => {
    console.log(payload);
  }
} as const;

so the event name is dynamic and can be added later on.

I defined the mitt emitter like so:

type EventName = keyof typeof events;
type Payload = { [key: string]: any };
type Events = { [key in EventName]: Payload };

export const analytics: Emitter<Events> = mitt<Events>();

and now i can call the event function:

analytics.on("*", (type, payload) => {
  const event = events[type];
  event?.(payload);
});

and this is works as expected:

analytics.emit("foo", { test: 1 }); // I get error on foo as it is not define in events object

However I cannot define an interface for events like so:

type EventsList = { [key: EventName]: (payload: Payload) => void };

so i'll get the events list like so:

const events: EventsList = {
  test: (payload) => {
    console.log(payload);
  },
  another: (payload) => {
    console.log(payload);
  }
} as const;

I get a ts error:

image

if i define it like this:

type EventsList = { [key: string]: (payload: Payload) => void };

it doesn't enforce the event name.

here is a codesandbox:
https://codesandbox.io/s/aged-microservice-oi72gv?file=/src/index.ts:0-558

I'd appreciate any help, thanks.

@khus29
Copy link

khus29 commented Jul 14, 2022

Hi @alonronin ,

Could you solve your issue?

@3rd
Copy link

3rd commented Jun 13, 2023

@alonronin Hey, I stumbled upon this issue while working on my own bus and thought although you probably don't need this anymore it's an interesting exercise.

Were you trying to define the schema in a single place, along with the handler implementation?
I don't get the "the event name is dynamic" part, and I find the usage super weird, as you declare all the handlers from the get-go, and infer the event types from the actual functions, so events is not an interface, it's the full implementation if I get it right.

Anyway, I think something like this does what you wanted, and should be easy to adjust to take in an interface type instead of the handlers: https://codesandbox.io/s/cool-leftpad-857rrd?file=/src/index.ts

const events = {
  withPrimitive: (payload: string) => {
    console.log(payload);
  },
  withObject: (payload: { a: boolean }) => {
    console.log(payload);
  }
};

type EventSchema<T extends Record<string, (payload: any) => void>> = {
  [K in keyof T]: Parameters<T[K]>[0];
};
type MittSchema = EventSchema<
  typeof events & {
    all: (payload: any) => any;
  }
>;
export const bus: Emitter<MittSchema> = mitt<MittSchema>();

bus.emit("withPrimitive", "hello"); // pass
bus.emit("withPrimitive", 123); // fail

@developit developit added the types label Jul 4, 2023
@stropho
Copy link

stropho commented Aug 5, 2023

@alonronin also stumbled upon this :) Getting back to the original example, it looks like there are 2 easy ways to solve this:

  1. explicitly type the payload in you events object - TS playground
  2. use the new satisfies keyword available from version 4.9 TS playground

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants