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

Better TypeScript type definitions #217

Closed
gfmio opened this issue Mar 2, 2020 · 2 comments
Closed

Better TypeScript type definitions #217

gfmio opened this issue Mar 2, 2020 · 2 comments

Comments

@gfmio
Copy link
Contributor

gfmio commented Mar 2, 2020

Hi!

Thank you for the work on this library and for providing type annotations. I would like to suggest an improvement to the type annotations.

Right now, if the type parameter is a map of events and parameter array types, the parameters themselves don't get human-readable names. If the event map type instead supported using entire function annotations, the parameter names could be retained which makes using the library easier.

Without the proposed changes, you could only do the following:

Screenshot 2020-03-02 at 14 46 23

With the proposed changes, you could do this:

Screenshot 2020-03-02 at 14 40 12

Additionally, I would also suggest to add an optional type parameter for the context object.

The type annotations required for this are as follows:

type ValidEventTypes<T = any> = string | symbol | T extends {
  [K in keyof T]: any[] | ((...args: any[]) => void);
}
  ? T
  : never;

type EventNames<T extends ValidEventTypes> = T extends string | symbol ? T : keyof T;

type Handler<T extends any[] | ((...args: any[]) => R), R = any> = T extends any[] ? (...args: T) => R : T;

type EventListener<T extends ValidEventTypes, K extends EventNames<T>> = T extends string | symbol
  ? (...args: any[]) => void
  : K extends keyof T
  ? Handler<T[K], void>
  : never;

type EventArgs<T extends ValidEventTypes, K extends EventNames<T>> = Parameters<EventListener<T, K>>;

/**
 * Minimal `EventEmitter` interface that is molded against the Node.js
 * `EventEmitter` interface.
 */
class EventEmitter<
  EventTypes extends string | symbol | { [K in keyof EventTypes]: any[] | ((...args: any[]) => void) } =
    | string
    | symbol,
  Context extends any = any
> {
  static prefixed: string | boolean;

  /**
   * Return an array listing the events for which the emitter has registered
   * listeners.
   */
  eventNames(): Array<EventNames<EventTypes>>;

  /**
   * Return the listeners registered for a given event.
   */
  listeners<T extends EventNames<EventTypes>>(event: T): Array<EventListener<EventTypes, T>>;

  /**
   * Return the number of listeners listening to a given event.
   */
  listenerCount(event: EventNames<EventTypes>): number;

  /**
   * Calls each of the listeners registered for a given event.
   */
  emit<T extends EventNames<EventTypes>>(event: T, ...args: EventArgs<EventTypes, T>): boolean;

  /**
   * Add a listener for a given event.
   */
  on<T extends EventNames<EventTypes>>(event: T, fn: EventListener<EventTypes, T>, context?: Context): this;
  addListener<T extends EventNames<EventTypes>>(event: T, fn: EventListener<EventTypes, T>, context?: Context): this;

  /**
   * Add a one-time listener for a given event.
   */
  once<T extends EventNames<EventTypes>>(event: T, fn: EventListener<EventTypes, T>, context?: Context): this;

  /**
   * Remove the listeners of a given event.
   */
  removeListener<T extends EventNames<EventTypes>>(
    event: T,
    fn?: EventListener<EventTypes, T>,
    context?: Context,
    once?: boolean,
  ): this;
  off<T extends EventNames<EventTypes>>(
    event: T,
    fn?: EventListener<EventTypes, T>,
    context?: Context,
    once?: boolean,
  ): this;

  /**
   * Remove all listeners, or those of the specified event.
   */
  removeAllListeners(event?: EventNames<EventTypes>): this;
}

namespace EventEmitter {
  export interface EventEmitterStatic {
    new <EventTypes extends ValidEventTypes>(): EventEmitter<EventTypes>;
  }

  export const EventEmitter: EventEmitterStatic;
}

export = EventEmitter;

Can I submit a PR for this change?

@lpinca
Copy link
Member

lpinca commented Mar 8, 2020

cc: @delta62

@vitaly-t
Copy link

vitaly-t commented Mar 12, 2020

In case there is any interest, you can avoid dealing with TypeScript definitions here completely, by using sub-events, which can wrap any EventEmitter into a strongly-typed event.

Here's example of wrapping EventEmitter3 into such a strongly-typed event:

import {EventEmitter} from 'eventemitter3';
import {fromEmitter} from "sub-events/ext";

const e = new EventEmitter(); // creating our test EventEmitter3, as P.O.C.

// Creating a strongly-typed event from EventEmitter3:
const event = fromEmitter<number>(e, 'receive');

// Now we can subscribe to the strongly-typed event:
const sub = event.subscribe(data => {
    console.log(data); // data is strongly-typed (number)
});

// Emitting some data on the source emitter, to test our POC:
e.emit('receive', 123);

sub.cancel(); // cancel the subscription when needed

More examples here.

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

No branches or pull requests

3 participants