0.1.8Updated 24 days ago
// deno-lint-ignore-file no-explicit-any

type Payload<T = any> = Record<string, unknown> & { instance?: T }
type Listener<T = any> = (payload: Payload<T>) => void;
type InstanceListener<T = any> = (payload: Payload<T>) => void;

type Constructor<T extends EventEmitter> = (new (...args: any[]) => T) & typeof EventEmitter

export class EventEmitter<Events extends string = any> {
  constructor(..._args: unknown[]) {}

  //region Class handlers

  private static __classListeners: Map<string, Map<string, Set<Listener>>> = new Map();
  private static get ClassListeners() {
    if(!this.__classListeners.has(this.name)) this.__classListeners.set(this.name, new Map());
    return this.__classListeners.get(this.name)!;
  }

  static on<T extends EventEmitter>(
    this: Constructor<T>,
    event: string,
    listener: InstanceListener<T>
  ) {
    if (!this.ClassListeners.has(event)) {
      this.ClassListeners.set(event, new Set());
    }
    this.ClassListeners.get(event)!.add(listener as Listener);
  }

  static once<T extends EventEmitter>(
    this: Constructor<T>,
    event: string,
    listener: InstanceListener<T>
  ) {
    const wrapper = (payload: Payload<T>) => {
      listener(payload);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }

  static off<T extends EventEmitter>(
    this: Constructor<T>,
    event: string,
    listener: InstanceListener<T>
  ) {
    this.ClassListeners.get(event)?.delete(listener as Listener);
  }

  static emit<T extends EventEmitter>(
    this: Constructor<T>,
    event: string,
    payload: Payload<T>
  ) {
    this.ClassListeners.get(event)?.forEach((listener) => listener(payload));
  }

  //region Instance handlers

  private __instanceListeners: Map<string, Set<Listener>> = new Map();  

  on<E extends Events>(event: E, listener: Listener<this>) {
    if (!this.__instanceListeners.has(event)) {
      this.__instanceListeners.set(event, new Set());
    }
    this.__instanceListeners.get(event)!.add(listener);
  }

  once<E extends Events>(event: E, listener: Listener<this>) {
    const wrapper = (payload: Payload<this>) => {
      listener(payload);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }

  off<E extends Events>(event: E, listener: Listener<this>) {
    this.__instanceListeners.get(event)?.delete(listener);
  }

  emit<E extends Events>(event: E): void {
    // Instance listeners
    this.__instanceListeners.get(event)?.forEach((listener) =>
      listener({ instance: this })
    );

    // Class-level listeners
    const constructor = this.constructor as Constructor<EventEmitter>;
    constructor.emit(event, { instance: this });
  }
}