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}`, ); } } } |