All files / src/libs GenericEvents.ts

100% Statements 23/23
100% Branches 33/33
100% Functions 8/8
100% Lines 23/23

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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189                                                                                                                        1x   18x             18x                                 18x 18x                                 6x   6x   6x   6x   6x   2x         4x                                       12x 12x   12x 12x                                             12x   12x   10x       10x 8x   8x         2x            
import { ILogger } from 'src/interfaces/ILogger';
 
/**
 * Interface for the callback.
 */
export interface ICallback {
    events: {
        [key: string]: IEvent<unknown, unknown>;
    };
}
 
/**
 * Interface for the events.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface IEvent<TData, TReturn = void> {
    name: string;
    data: TData;
}
 
/**
 * Interface for the events.
 */
type RegisteredEvent<T extends ICallback, K extends keyof T['events']> = {
    eventName: K;
    callback: (
        data: T['events'][K]['data'],
    ) => T['events'][K] extends IEvent<unknown, infer TReturn>
        ? TReturn
        : unknown;
};
 
/**
 * Events class; encapsulates event registration and firing
 * @example
 * ```typescript
 * // Define a concrete interface for your specific events
 * interface MyEvents extends ICallback {
 *     events: {
 *         'myEvent1': IEvent<string, number>; // Event with a string as input and a number as output
 *         'myEvent2': IEvent<number, void>;   // Event with a number as input and no output
 *         // Add more events here
 *     };
 * }
 * 
 * // Use the Events class with your concrete interface
 * const eventsInstance = new GenericEvents<MyEvents>();
 * 
 * // Register event one with a string as input and a number as output
 * eventsInstance.registerEvent('myEvent1', (data) => {
 *     console.log(data);
 *     return 100;
 * });
 
 * // Fire event one with a string as input and a number as output
 * eventsInstance.fireEvent('myEvent1', 'Fire event 1', (result) => {
 *     console.log(result); // Result is a number
 * });
 * ```
 */
export default class GenericEvents<T extends ICallback> {
    private readonly _logger: ILogger | undefined;
    private _events: Array<RegisteredEvent<T, keyof T['events']>> = [];
 
    /**
     * Creates a new Events instance
     * @param logger The logger to use. You can use your own logger or `console` as logger.
     */
    constructor(logger?: ILogger) {
        this._logger = logger;
    }
 
    /**
     * Registers an event with a callback.
     * @param eventName The name of the event to register.
     * @param callback The callback to execute when the event is fired.
     */
    public registerEvent<K extends keyof T['events']>(
        eventName: K,
        callback: (
            data: T['events'][K]['data'],
        ) => T['events'][K] extends IEvent<unknown, infer TReturn>
            ? TReturn
            : unknown,
    ): void {
        // Add the event to the _events array
        this._events.push({ eventName, callback });
        this._logger?.debug(`Event ${eventName.toString()} registered`);
    }
 
    /**
     * Deregisters an event with a callback.
     * @param eventName The name of the event to deregister.
     * @param callback The callback to deregister.
     */
    public deregisterEvent<K extends keyof T['events']>(
        eventName: K,
        callback: (
            data: T['events'][K]['data'],
        ) => T['events'][K] extends IEvent<unknown, infer TReturn>
            ? TReturn
            : unknown,
    ): void {
        // Delete the event from the _events array
        const initialLength = this._events.length;
 
        this._events = this._events.filter(
            (event) =>
                event.eventName !== eventName || event.callback !== callback,
        );
        const finalLength = this._events.length;
 
        if (finalLength === initialLength) {
            // No event was removed, log a warning
            this._logger?.warn(
                `Event ${eventName.toString()} could not be deregistered`,
            );
        } else {
            // Event was removed, log a debug message
            this._logger?.debug(`Event ${eventName.toString()} deregistered`);
        }
    }
 
    /**
     * Fires an event with a callback.
     * @param eventName The name of the event to fire.
     * @param eventData The data to pass to the event handler.
     * @param callback The callback to execute when the event is fired.
     */
    public fireEvent<K extends keyof T['events']>(
        eventName: K,
        eventData: T['events'][K]['data'],
        callback?: (
            result: T['events'][K] extends IEvent<unknown, infer TReturn>
                ? TReturn
                : unknown,
        ) => void,
    ): void {
        // Find the event in the _events array and execute the callback
        this._events
            .filter((event) => event.eventName === eventName)
            .forEach((event) => {
                this._executeEventHandler(event.callback, eventData, callback);
                this._logger?.debug(`Event ${eventName.toString()} fired`);
            });
    }
 
    /**
     * Executes an event handler and calls the callback with the result.
     * @param handler The event handler to execute.
     * @param eventData The data to pass to the event handler.
     * @param callback The callback to execute when the event handler is executed.
     */
    private async _executeEventHandler<K extends keyof T['events']>(
        handler: (
            data: T['events'][K]['data'],
        ) => T['events'][K] extends IEvent<unknown, infer TReturn>
            ? TReturn
            : unknown,
        eventData: T['events'][K]['data'],
        callback?: (
            result: T['events'][K] extends IEvent<unknown, infer TReturn>
                ? TReturn
                : unknown,
        ) => void,
    ): Promise<void> {
        try {
            // Execute the handler and call the callback with the result
            const result = await handler(eventData);
 
            this._logger?.debug(
                `Event handler for ${handler.toString()} executed`,
            );
 
            if (callback) {
                callback(result);
 
                this._logger?.debug(
                    `Callback for ${handler.toString()} executed`,
                );
            }
        } catch (error) {
            this._logger?.error(
                `Error in event handler for ${handler.toString()}: ${error}`,
            );
        }
    }
}
 
Zur TypeDoc-Dokumentation