All files / src/internal/lifecycle lifecycle-event-bus.mts

89.65% Statements 26/29
86.66% Branches 13/15
87.5% Functions 7/8
89.65% Lines 26/29

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                            268x 268x             144x 144x 123x     144x 144x 123x     144x   144x 120x 120x 99x   120x 99x           526x 513x     13x   13x   526x 13x   13x 13x                 13x     13x   13x        
/* eslint-disable @typescript-eslint/no-empty-object-type */
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
 
type ListenersMap = Map<string, Map<string, Set<Function>>>
 
/**
 * Event bus for service lifecycle events (create, destroy, etc.).
 *
 * Enables loose coupling between services by allowing them to subscribe
 * to lifecycle events of their dependencies without direct references.
 * Used primarily for invalidation cascading.
 */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
export class LifecycleEventBus {
  private listeners: ListenersMap = new Map()
  constructor(private readonly logger: Console | null = null) {}
 
  on<Event extends string | `pre:${string}` | `post:${string}`>(
    ns: string,
    event: Event,
    listener: (event: Event) => void,
  ) {
    this.logger?.debug(`[LifecycleEventBus]#on(): ns:${ns} event:${event}`)
    if (!this.listeners.has(ns)) {
      this.listeners.set(ns, new Map())
    }
 
    const nsEvents = this.listeners.get(ns)!
    if (!nsEvents.has(event)) {
      nsEvents.set(event, new Set())
    }
 
    nsEvents.get(event)!.add(listener)
 
    return () => {
      nsEvents.get(event)?.delete(listener)
      if (nsEvents.get(event)?.size === 0) {
        nsEvents.delete(event)
      }
      if (nsEvents.size === 0) {
        this.listeners.delete(ns)
      }
    }
  }
 
  async emit(key: string, event: string) {
    if (!this.listeners.has(key)) {
      return
    }
 
    const events = this.listeners.get(key)!
 
    this.logger?.debug(`[LifecycleEventBus]#emit(): ${key}:${event}`)
 
    const res = await Promise.allSettled(
      [...(events.get(event) ?? [])!].map((listener) => listener(event)),
    ).then((results) => {
      const res = results
        .filter((result) => result.status === 'rejected')
        .map((result: PromiseRejectedResult) => {
          this.logger?.warn(
            `[LifecycleEventBus]#emit(): ${key}:${event} rejected with`,
            result.reason,
          )
          return result
        })
 
      Iif (res.length > 0) {
        return Promise.reject(res)
      }
      return results
    })
    return res
  }
}