UNPKG

@graphql-markdown/core

Version:

GraphQL-Markdown core package for generating Markdown documentation from a GraphQL schema.

156 lines (155 loc) 4.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resetEvents = exports.getEvents = void 0; const node_events_1 = require("node:events"); /** * Custom EventEmitter that supports cancellable events with sequential handler execution. * * Features: * - Executes async handlers sequentially (one after another) * - Collects errors without stopping handler execution * - Supports stopPropagation to halt handler chain * - Returns execution results to emitters * * @category Events */ class CancellableEventEmitter extends node_events_1.EventEmitter { static toError(error) { return error instanceof Error ? error : new Error(String(error)); } static isDefaultPrevented(event) { return "defaultPrevented" in event ? !!event.defaultPrevented : false; } /** * Emit an event asynchronously with cancellable event support. * * Handlers are executed sequentially in registration order. * If a handler throws an error, it's collected but execution continues. * If event.propagationStopped becomes true, remaining handlers are skipped. * * After all handlers execute, if the event has a defaultAction function and * preventDefault() was not called, the default action is automatically executed. * * @param eventName - Name of the event to emit * @param event - Cancellable event object with preventDefault() and stopPropagation() methods * @returns Promise resolving to EmitResult with errors and cancellation status * * @example * ```typescript * const events = getEvents(); * const event = new SchemaLoadEvent({ * schemaLocation: '/path/to/schema', * defaultAction: async () => { * // This runs automatically if not prevented * await loadSchema('/path/to/schema'); * } * }); * * const { errors, defaultPrevented } = await events.emitAsync('beforeLoadSchema', event); * * if (errors.length > 0) { * console.error('Errors occurred:', errors); * } * ``` */ async emitAsync(eventName, event) { const errors = []; // Get all registered listeners for this event const listeners = this.listeners(eventName); // Check if event is a full cancellable event (has propagationStopped property) const isCancellable = "propagationStopped" in event; await this.emitToListeners(listeners, event, errors, isCancellable); await this.runDefaultAction(event, errors); return { errors, defaultPrevented: CancellableEventEmitter.isDefaultPrevented(event), }; } async invokeListener(listener, event) { await Promise.resolve(listener(event)); } async emitToListeners(listeners, event, errors, isCancellable) { for (const listener of listeners) { try { await this.invokeListener(listener, event); if (isCancellable && event.propagationStopped) { break; } } catch (error) { errors.push(CancellableEventEmitter.toError(error)); } } } async runDefaultAction(event, errors) { if (!("runDefaultAction" in event)) { return; } try { await event.runDefaultAction(); } catch (error) { errors.push(CancellableEventEmitter.toError(error)); } } } /** * Singleton instance of the CancellableEventEmitter. * Shared across the entire application. */ let instance = null; /** * Get the singleton event emitter instance. * * Creates the instance on first call, then returns the same instance on subsequent calls. * This ensures all parts of the application share the same event bus. * * @returns The singleton CancellableEventEmitter instance * * @example * ```typescript * // In generator.ts * const events = getEvents(); * events.on('beforeLoadSchema', handler); * * // In renderer.ts - same instance! * const events = getEvents(); * await events.emitAsync('beforeLoadSchema', event); * ``` * * @category Events */ const getEvents = () => { instance ??= new CancellableEventEmitter(); return instance; }; exports.getEvents = getEvents; /** * Reset the event emitter singleton. * * Removes all event listeners and clears the instance. * The next call to getEvents() will create a fresh instance. * * **Important:** This should only be used in tests to ensure test isolation. * * @example * ```typescript * // In test setup * afterEach(() => { * resetEvents(); * jest.restoreAllMocks(); * }); * ``` * * @category Events */ const resetEvents = () => { if (instance) { instance.removeAllListeners(); } instance = null; }; exports.resetEvents = resetEvents;