All files / src event-emitter.mts

100% Statements 23/23
100% Branches 8/8
100% Functions 7/7
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                                        4x 4x 21x           123x 118x     123x   123x 103x               107x 2x     105x 105x 104x               2x 1x   1x     2x             21x 7x     14x   18x          
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-empty-object-type */
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
 
import { Injectable } from './decorators/index.mjs'
import { InjectableScope } from './enums/index.mjs'
 
/* eslint-disable @typescript-eslint/no-explicit-any */
export type EventsConfig = {
  [event: string]: any[]
}
export type EventsNames<Events extends EventsConfig> = Exclude<
  keyof Events,
  symbol | number
>
export type EventsArgs<
  Events extends EventsConfig,
  Name extends EventsNames<Events>,
> = Events[Name] extends any[] ? Events[Name] : []
 
@Injectable({ scope: InjectableScope.Transient })
export class EventEmitter<Events extends EventsConfig = {}> {
  private listeners: Map<EventsNames<Events>, Set<Function>> = new Map()
 
  on<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
    event: E,
    listener: (...args: Args) => void | Promise<void>,
  ) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set())
    }
 
    this.listeners.get(event)!.add(listener)
 
    return () => {
      this.off(event, listener)
    }
  }
 
  off<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
    event: E,
    listener: (...args: Args) => void | Promise<void>,
  ) {
    if (!this.listeners.has(event)) {
      return
    }
 
    this.listeners.get(event)!.delete(listener)
    if (this.listeners.get(event)!.size === 0) {
      this.listeners.delete(event)
    }
  }
 
  once<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
    event: E,
    listener: (...args: Args) => void | Promise<void>,
  ) {
    const off = this.on(event, (...args) => {
      off()
      // @ts-expect-error - This is a valid call
      listener(...args)
    })
 
    return off
  }
 
  async emit<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
    event: E,
    ...args: Args
  ): Promise<any> {
    if (!this.listeners.has(event)) {
      return
    }
 
    return Promise.all(
      Array.from(this.listeners.get(event)!).map((listener) =>
        listener(...args),
      ),
    )
  }
}