1

I'm trying to create a typescript enforcer over EventEmitter. My goal is to create a emitter with addListener, removeListener and emit methods that will break on compiler time if I pass a invalid event or even if I pass a function that doesn't match with event signature (args and return).

For this to be possible, I'll need a Map interface (some interface with a list of functions) an example of this can be seen on type native GlobalEventHandlersEventMap in:

lib.dom.d.ts

//...
interface GlobalEventHandlersEventMap {
  "abort": UIEvent;
  "animationcancel": AnimationEvent;
  "animationend": AnimationEvent;
  "animationiteration": AnimationEvent;
  //...

So far I got the first two methods right:

import { EventEmitter } from "events";

// A simple mapping interface
interface SomeMap {
  greeting: (message: string) => void;
}

// This generalization of function is necessary to track
// the list of arguments and result types.
interface GFunc<TArgs extends any[] = any[], TResult = any> {
  (...args: TArgs): TResult;
}

// This is a advanced type to mask the EventEmitter class
interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  addListener<K extends keyof T>(event: K, listener: T[K]): void;
  removeListener<K extends keyof T>(event: K, listener: T[K]): void;
}

const emitter: AdvEventEmitter<SomeMap> = new EventEmitter();

emitter.addListener('greeting', msg => {
  console.log(msg);
});

In the code above, AdvEventEmitter interface is capable to enforce constraint on the first parameter:

emitter.addListener('something_else', () => {});

Msg: Argument of type '"something_else"' is not assignable to parameter of type '"greeting"'.

And even enforce types and quantity of arguments in the second parameter:

emitter.addListener('greeting', (m1, m2) => {
  console.log(m1, m2);
});

Msg: Argument of type '(m1: any, m2: any) => void' is not assignable to parameter of
type '(message: string) => void'.

Great.

Now the problem is the emit method.

I'm trying something like this:

interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  // ...
  emit<K extends keyof T, TArgs extends any[] = any[]>(event: K, ...args: TArgs): void;
}

the arg event is validated correctly (as expected), but the args just a generic list of any. I'm don't know how to connect TArgs constraints with K constraints.

Is there a way to enforce this constrain?

1 Answer 1

1

You need to extract the parameters of the K function from T. You can do this using the predefined Parameters conditional type:


interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void;
}

Playground Link

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.