diff --git a/src/__tests__/context.ts b/src/__tests__/context.ts index 0bf75fa..671d02c 100644 --- a/src/__tests__/context.ts +++ b/src/__tests__/context.ts @@ -1,9 +1,10 @@ import { EventNotifier } from '../index'; -export type Data = { - balance: { value: number }; - test: undefined; -}; +export interface Data { + balanceChanged: number; + updated: null; + entityAdded: { id: number; name: string }; +} export type Context = { eventNotifier: EventNotifier; diff --git a/src/__tests__/notifier.bench.ts b/src/__tests__/notifier.bench.ts index 5d5cf77..c7bbb20 100644 --- a/src/__tests__/notifier.bench.ts +++ b/src/__tests__/notifier.bench.ts @@ -4,21 +4,35 @@ import { EventNotifier } from '../index'; import { Data } from './context'; describe('benchmarking', () => { - const fn = ({ value = 1 }) => { - value + 1; - }; - bench('on method without data', () => { const eventNotifier = new EventNotifier(); - eventNotifier.on('test', fn); - eventNotifier.emit({ type: 'test' }); - eventNotifier.off('test', fn); + const fn = (data: unknown) => { + console.log(data); + }; + eventNotifier.on('updated', fn); + eventNotifier.emit('updated', null); + eventNotifier.off('updated', fn); }); - bench('on method with data', () => { + bench('on method with primitive data', () => { const eventNotifier = new EventNotifier(); - eventNotifier.on('balance', fn); - eventNotifier.emit({ type: 'balance', value: 42 }); - eventNotifier.off('balance', fn); + const fn = (data: number) => { + console.log(data); + }; + eventNotifier.on('balanceChanged', fn); + eventNotifier.emit('balanceChanged', 42); + eventNotifier.off('balanceChanged', fn); }); }); + +bench('on method with object data', () => { + const eventNotifier = new EventNotifier(); + const fn = (data: { id: number; name: string }) => { + console.log(data); + }; + eventNotifier.on('entityAdded', fn); + eventNotifier.emit('entityAdded', { id: 42, name: 'name' }); + eventNotifier.off('entityAdded', fn); +}); + +// document.createElement('div').dispatchEvent({}) diff --git a/src/eventNotifier.ts b/src/eventNotifier.ts new file mode 100644 index 0000000..b03bad9 --- /dev/null +++ b/src/eventNotifier.ts @@ -0,0 +1,54 @@ +import { IEventNotifier, NotifierListener, NotifyOptions } from './types'; + +export class EventNotifier implements IEventNotifier { + readonly #listenerMap = new Map>(); + + public emit(name: TName, data: TNotifierData[TName]): void { + this.#listenerMap.get(name)?.forEach((options, listener, map) => { + if (options?.once) map.delete(listener); + void listener(data); + }); + } + + public on( + name: TName, + listener: NotifierListener, + options?: NotifyOptions, + ): void { + let listeners = this.#listenerMap.get(name); + if (!listeners) { + listeners = new Map(); + this.#listenerMap.set(name, listeners); + } + listeners.set(listener, options); + } + + public off(name: TName, listener: NotifierListener): void { + const listeners = this.#listenerMap.get(name); + listeners?.delete(listener); + } + + public once( + name: TName, + listener: NotifierListener, + options?: Omit, + ) { + this.on(name, listener, { ...options, once: true }); + } + + public hasListener( + name: TName, + listener: NotifierListener, + ): boolean { + const listeners = this.#listenerMap.get(name); + return !!listeners && listeners.has(listener); + } + + public deleteListeners(name: TName): void { + this.#listenerMap.delete(name); + } + + public clearAll(): void { + this.#listenerMap.clear(); + } +} diff --git a/src/index.ts b/src/index.ts index 91d1208..19bdf34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,69 +1,2 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type EventNotifierListener = (data: TData extends object ? TData : object) => void | Promise; - -export type NotifyType = { type: TType }; - -export type NotifyListenerOptions = { - readonly once?: boolean; -}; - -export class EventNotifier> { - protected listenerMap = new Map< - keyof TEventNotifierData, - Map - >(); - - public emit( - event: TEventNotifierData[TType] extends object ? NotifyType & TEventNotifierData[TType] : NotifyType, - ): void { - const { type, ...data } = event; - this.listenerMap.get(type)?.forEach((options, listener, map) => { - if (options?.once) map.delete(listener); - void listener(data); - }); - } - - public on( - type: TType, - listener: EventNotifierListener, - options?: NotifyListenerOptions, - ): void { - const listeners = this.listenerMap.get(type) ?? new Map(); - listeners.set(listener, options); - this.listenerMap.set(type, listeners); - } - - public off( - type: TType, - listener: EventNotifierListener, - ): void { - this.listenerMap.get(type)?.forEach((_, currentListener, map) => { - if (currentListener !== listener) return; - map.delete(listener); - }); - } - - public once( - type: TType, - listener: EventNotifierListener, - options?: Omit, - ) { - this.on(type, listener, { ...options, once: true }); - } - - public has( - type: TType, - listener: EventNotifierListener, - ): boolean { - const listeners = this.listenerMap.get(type); - return !!listeners && listeners.has(listener); - } - - public delete(type: TType): void { - this.listenerMap.delete(type); - } - - public clear(): void { - this.listenerMap.clear(); - } -} +export * from './eventNotifier'; +export * from './types'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..eff3cd7 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,12 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type NotifierListener = (data: TData) => void | Promise; + +export type NotifyOptions = { + readonly once?: boolean; +}; + +export interface IEventNotifier { + on(name: TName, listener: NotifierListener): void; + once(name: TName, listener: NotifierListener): void; + off(name: TName, listener: NotifierListener): void; +}