Skip to content

Commit

Permalink
Merge pull request #120 from ksv90/dev
Browse files Browse the repository at this point in the history
feat: types changed
  • Loading branch information
ksv90 authored May 16, 2024
2 parents da2abb9 + c7c0984 commit b6a79c2
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 84 deletions.
9 changes: 5 additions & 4 deletions src/__tests__/context.ts
Original file line number Diff line number Diff line change
@@ -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<Data>;
Expand Down
36 changes: 25 additions & 11 deletions src/__tests__/notifier.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data>();
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<Data>();
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<Data>();
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({})
54 changes: 54 additions & 0 deletions src/eventNotifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { IEventNotifier, NotifierListener, NotifyOptions } from './types';

export class EventNotifier<TNotifierData extends object> implements IEventNotifier<TNotifierData> {
readonly #listenerMap = new Map<keyof TNotifierData, Map<NotifierListener, NotifyOptions | undefined>>();

public emit<TName extends keyof TNotifierData>(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<TName extends keyof TNotifierData>(
name: TName,
listener: NotifierListener<TNotifierData[TName]>,
options?: NotifyOptions,
): void {
let listeners = this.#listenerMap.get(name);
if (!listeners) {
listeners = new Map<NotifierListener, NotifyOptions>();
this.#listenerMap.set(name, listeners);
}
listeners.set(listener, options);
}

public off<TName extends keyof TNotifierData>(name: TName, listener: NotifierListener<TNotifierData[TName]>): void {
const listeners = this.#listenerMap.get(name);
listeners?.delete(listener);
}

public once<TName extends keyof TNotifierData>(
name: TName,
listener: NotifierListener<TNotifierData[TName]>,
options?: Omit<NotifyOptions, 'once'>,
) {
this.on(name, listener, { ...options, once: true });
}

public hasListener<TName extends keyof TNotifierData>(
name: TName,
listener: NotifierListener<TNotifierData[TName]>,
): boolean {
const listeners = this.#listenerMap.get(name);
return !!listeners && listeners.has(listener);
}

public deleteListeners<TName extends keyof TNotifierData>(name: TName): void {
this.#listenerMap.delete(name);
}

public clearAll(): void {
this.#listenerMap.clear();
}
}
71 changes: 2 additions & 69 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type EventNotifierListener<TData = any> = (data: TData extends object ? TData : object) => void | Promise<void>;

export type NotifyType<TType> = { type: TType };

export type NotifyListenerOptions = {
readonly once?: boolean;
};

export class EventNotifier<TEventNotifierData extends Record<string, object | undefined>> {
protected listenerMap = new Map<
keyof TEventNotifierData,
Map<EventNotifierListener, NotifyListenerOptions | undefined>
>();

public emit<TType extends keyof TEventNotifierData>(
event: TEventNotifierData[TType] extends object ? NotifyType<TType> & TEventNotifierData[TType] : NotifyType<TType>,
): void {
const { type, ...data } = event;
this.listenerMap.get(type)?.forEach((options, listener, map) => {
if (options?.once) map.delete(listener);
void listener(data);
});
}

public on<TType extends keyof TEventNotifierData>(
type: TType,
listener: EventNotifierListener<TEventNotifierData[TType]>,
options?: NotifyListenerOptions,
): void {
const listeners = this.listenerMap.get(type) ?? new Map<EventNotifierListener, NotifyListenerOptions>();
listeners.set(listener, options);
this.listenerMap.set(type, listeners);
}

public off<TType extends keyof TEventNotifierData>(
type: TType,
listener: EventNotifierListener<TEventNotifierData[TType]>,
): void {
this.listenerMap.get(type)?.forEach((_, currentListener, map) => {
if (currentListener !== listener) return;
map.delete(listener);
});
}

public once<TType extends keyof TEventNotifierData>(
type: TType,
listener: EventNotifierListener<TEventNotifierData[TType]>,
options?: Omit<NotifyListenerOptions, 'once'>,
) {
this.on(type, listener, { ...options, once: true });
}

public has<TType extends keyof TEventNotifierData>(
type: TType,
listener: EventNotifierListener<TEventNotifierData[TType]>,
): boolean {
const listeners = this.listenerMap.get(type);
return !!listeners && listeners.has(listener);
}

public delete<TType extends keyof TEventNotifierData>(type: TType): void {
this.listenerMap.delete(type);
}

public clear(): void {
this.listenerMap.clear();
}
}
export * from './eventNotifier';
export * from './types';
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type NotifierListener<TData = any> = (data: TData) => void | Promise<void>;

export type NotifyOptions = {
readonly once?: boolean;
};

export interface IEventNotifier<TNotifierData extends object> {
on<TName extends keyof TNotifierData>(name: TName, listener: NotifierListener<TNotifierData[TName]>): void;
once<TName extends keyof TNotifierData>(name: TName, listener: NotifierListener<TNotifierData[TName]>): void;
off<TName extends keyof TNotifierData>(name: TName, listener: NotifierListener<TNotifierData[TName]>): void;
}

0 comments on commit b6a79c2

Please sign in to comment.