0.1.6Updated a month ago
type EventMap = Record<string, any[]>
type EventListener<T extends EventMap, K extends keyof T> = (...args: T[K]) => void | Promise<void>
type TypedEventMap<T extends EventMap> = { [K in keyof T]?: Array<EventListener<T, K>> }
type SuperEventListener<P extends CustomEventEmitter, T extends EventMap, K extends keyof T> = (instance: P, ...args: T[K]) => void | Promise<void>
type TypedSuperEventMap<P extends CustomEventEmitter, T extends EventMap> = { [K in keyof T]?: Array<SuperEventListener<P, T, K>> }

export class CustomEventEmitter<T extends EventMap = EventMap> {
  constructor(..._: any[]) {}

  protected _events: TypedEventMap<T> = {};

  protected get event_constructor() {
    return this.constructor.name;
  }

  protected get _super_events() {
    return (CustomEventEmitter.__events[this.event_constructor] ||= {}) as TypedSuperEventMap<CustomEventEmitter<any>, T>;
  }

  on<K extends keyof T>(event: K, listener: EventListener<T, K>): this {
    (this._events[event] ||= []).push(listener);

    return this;
  }

  off<K extends keyof T>(event: K, listener: EventListener<T, K>): this {
    const index = this._events[event]?.indexOf(listener);
    if(typeof index === "number" && index > -1) this._events[event]!.splice(index, 1);
    return this;
  }

  async emit<K extends keyof T>(event: K, ...args: T[K]): Promise<boolean> {
    const event_listeners = (this._events[event] || []);
    const super_event_listeners = this._super_events[event] || [];

    for(const listener of event_listeners) {
      await listener(...args);
    }

    for(const listener of super_event_listeners) {
      await listener(this, ...args as T[K]);
    }

    return true;
  }

  protected static __events: Record<string, TypedEventMap<EventMap>> = {};
  private static get _events() {
    return this.__events[this.name] ||= {};
  }

  static on<
    InstanceType extends CustomEventEmitter,
    eventName extends keyof InstanceType['__event_map']
  >(
    this: Constructor<InstanceType> & typeof CustomEventEmitter<any>,
    event: eventName,
    listener: (instance: InstanceType, ...args: InstanceType['__event_map'][eventName] ) => any
  ) {
    (this._events[event.toString()] ||= []).push(listener);

    return this;
  }

  static off<
    InstanceType extends CustomEventEmitter,
    eventName extends keyof InstanceType['__event_map']
  >(
    this: Constructor<InstanceType> & typeof CustomEventEmitter<any>,
    event: eventName,
    listener: (instance: InstanceType, ...args: InstanceType['__event_map'][eventName]) => any
  ) {
    const index = this._events[event.toString()]?.indexOf(listener as any);
    if(typeof index === "number" && index > -1) this._events[event.toString()]!.splice(index, 1);
    return this;
  }

  declare __event_map: T
}

type Constructor<T> = new (...args: any[]) => T;