@graphql-markdown/core
Version:
GraphQL-Markdown core package for generating Markdown documentation from a GraphQL schema.
156 lines (155 loc) • 4.94 kB
JavaScript
;
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;