import { Observer } from 'rxjs' import { Observable } from 'rxjs' export type ALL = '__ALL__' const ALL: ALL = '__ALL__' interface State { callChain: Set observables: Map[]> observers: Map[]> options: Options } export type Options = { onCycle(chain: (keyof Messages | ALL)[]): void isDevMode: boolean } export class Emitter { private emitterState: State constructor(options?: Partial>) { let DEFAULT_OPTIONS: Options = { isDevMode: false, onCycle(chain) { console.error( '[typed-rx-emitter] Error: Cyclical dependency detected. ' + 'This may cause a stack overflow unless you fix it. ' + chain.join(' -> ') ) } } this.emitterState = { callChain: new Set, observables: new Map, observers: new Map, options: { ...DEFAULT_OPTIONS, ...options } } } /** * Emit an event (silently fails if no listeners are hooked up yet) */ emit(key: K, value: Messages[K]): this { let { isDevMode, onCycle } = this.emitterState.options if (isDevMode) { if (this.emitterState.callChain.has(key)) { onCycle(Array.from(this.emitterState.callChain).concat(key)) return this } else { this.emitterState.callChain.add(key) } } if (this.hasChannel(key)) { this.emitOnChannel(key, value) } if (this.hasChannel(ALL)) { this.emitOnChannel(ALL, value) } if (isDevMode) this.emitterState.callChain.clear() return this } /** * Subscribe to an event */ on(key: K): Observable { return this.createChannel(key) } /** * Subscribe to all events */ all(): Observable { return this.createChannel(ALL) } ///////////////////// privates ///////////////////// private createChannel(key: K | ALL) { if (!this.emitterState.observers.has(key)) { this.emitterState.observers.set(key, []) } if (!this.emitterState.observables.has(key)) { this.emitterState.observables.set(key, []) } const observable: Observable = Observable .create((_: Observer) => { this.emitterState.observers.get(key)!.push(_) return () => this.deleteChannel(key, observable) }) this.emitterState.observables.get(key)!.push(observable) return observable } private deleteChannel( key: K | ALL, observable: Observable ) { if (!this.emitterState.observables.has(key)) { return } const array = this.emitterState.observables.get(key)! const index = array.indexOf(observable) if (index < 0) { return } array.splice(index, 1) if (!array.length) { this.emitterState.observables.delete(key) this.emitterState.observers.delete(key) } } private emitOnChannel( key: K | ALL, value: Messages[K] ) { this.emitterState.observers.get(key)!.forEach(_ => _.next(value)) } private hasChannel(key: K | ALL): boolean { return this.emitterState.observables.has(key) } }