277 lines
7.4 KiB
JavaScript
277 lines
7.4 KiB
JavaScript
/**
|
|
* V3 Hooks System - Hook Registry
|
|
*
|
|
* Central registry for managing hook definitions and lifecycle.
|
|
* Provides registration, unregistration, and discovery of hooks.
|
|
*
|
|
* @module v3/shared/hooks/registry
|
|
*/
|
|
import { HookPriority, } from './types.js';
|
|
/**
|
|
* Hook registry implementation
|
|
*/
|
|
export class HookRegistry {
|
|
hooks = new Map();
|
|
hooksById = new Map();
|
|
hookIdCounter = 0;
|
|
// Statistics tracking
|
|
stats = {
|
|
executions: 0,
|
|
failures: 0,
|
|
totalExecutionTime: 0,
|
|
};
|
|
/**
|
|
* Register a new hook
|
|
*
|
|
* @param event - Hook event type
|
|
* @param handler - Hook handler function
|
|
* @param priority - Hook priority (default: Normal)
|
|
* @param options - Additional hook options
|
|
* @returns Hook ID for later unregistration
|
|
*/
|
|
register(event, handler, priority = HookPriority.Normal, options = {}) {
|
|
// Generate unique hook ID
|
|
const id = `hook_${++this.hookIdCounter}_${Date.now()}`;
|
|
// Create hook definition
|
|
const definition = {
|
|
id,
|
|
event,
|
|
handler,
|
|
priority,
|
|
name: options.name,
|
|
enabled: options.enabled ?? true,
|
|
timeout: options.timeout,
|
|
metadata: options.metadata,
|
|
};
|
|
// Add to event-specific list
|
|
let eventHooks = this.hooks.get(event);
|
|
if (!eventHooks) {
|
|
eventHooks = [];
|
|
this.hooks.set(event, eventHooks);
|
|
}
|
|
eventHooks.push(definition);
|
|
// Sort by priority (highest first)
|
|
eventHooks.sort((a, b) => b.priority - a.priority);
|
|
// Add to ID map
|
|
this.hooksById.set(id, definition);
|
|
return id;
|
|
}
|
|
/**
|
|
* Unregister a hook by ID
|
|
*
|
|
* @param hookId - Hook ID to unregister
|
|
* @returns Whether hook was found and removed
|
|
*/
|
|
unregister(hookId) {
|
|
const definition = this.hooksById.get(hookId);
|
|
if (!definition) {
|
|
return false;
|
|
}
|
|
// Remove from event-specific list
|
|
const eventHooks = this.hooks.get(definition.event);
|
|
if (eventHooks) {
|
|
const index = eventHooks.findIndex(h => h.id === hookId);
|
|
if (index !== -1) {
|
|
eventHooks.splice(index, 1);
|
|
}
|
|
// Clean up empty arrays
|
|
if (eventHooks.length === 0) {
|
|
this.hooks.delete(definition.event);
|
|
}
|
|
}
|
|
// Remove from ID map
|
|
this.hooksById.delete(hookId);
|
|
return true;
|
|
}
|
|
/**
|
|
* Unregister all hooks for an event
|
|
*
|
|
* @param event - Event type to clear hooks for
|
|
* @returns Number of hooks removed
|
|
*/
|
|
unregisterAll(event) {
|
|
if (event) {
|
|
const eventHooks = this.hooks.get(event) || [];
|
|
const count = eventHooks.length;
|
|
// Remove from ID map
|
|
for (const hook of eventHooks) {
|
|
this.hooksById.delete(hook.id);
|
|
}
|
|
// Clear event hooks
|
|
this.hooks.delete(event);
|
|
return count;
|
|
}
|
|
else {
|
|
// Clear all hooks
|
|
const count = this.hooksById.size;
|
|
this.hooks.clear();
|
|
this.hooksById.clear();
|
|
this.hookIdCounter = 0;
|
|
return count;
|
|
}
|
|
}
|
|
/**
|
|
* Get all hooks for a specific event (sorted by priority)
|
|
*
|
|
* @param event - Event type
|
|
* @param includeDisabled - Whether to include disabled hooks
|
|
* @returns Array of hook definitions
|
|
*/
|
|
getHandlers(event, includeDisabled = false) {
|
|
const eventHooks = this.hooks.get(event) || [];
|
|
if (includeDisabled) {
|
|
return [...eventHooks];
|
|
}
|
|
return eventHooks.filter(h => h.enabled);
|
|
}
|
|
/**
|
|
* Get a hook by ID
|
|
*
|
|
* @param hookId - Hook ID
|
|
* @returns Hook definition or undefined
|
|
*/
|
|
getHook(hookId) {
|
|
return this.hooksById.get(hookId);
|
|
}
|
|
/**
|
|
* Enable a hook
|
|
*
|
|
* @param hookId - Hook ID
|
|
* @returns Whether hook was found and enabled
|
|
*/
|
|
enable(hookId) {
|
|
const hook = this.hooksById.get(hookId);
|
|
if (hook) {
|
|
hook.enabled = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Disable a hook
|
|
*
|
|
* @param hookId - Hook ID
|
|
* @returns Whether hook was found and disabled
|
|
*/
|
|
disable(hookId) {
|
|
const hook = this.hooksById.get(hookId);
|
|
if (hook) {
|
|
hook.enabled = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* List all registered hooks
|
|
*
|
|
* @param filter - Optional filter options
|
|
* @returns Array of hook definitions
|
|
*/
|
|
listHooks(filter) {
|
|
let hooks;
|
|
if (filter?.event) {
|
|
hooks = this.hooks.get(filter.event) || [];
|
|
}
|
|
else {
|
|
hooks = Array.from(this.hooksById.values());
|
|
}
|
|
// Apply filters
|
|
if (filter?.enabled !== undefined) {
|
|
hooks = hooks.filter(h => h.enabled === filter.enabled);
|
|
}
|
|
if (filter?.minPriority !== undefined) {
|
|
const minPriority = filter.minPriority;
|
|
hooks = hooks.filter(h => h.priority >= minPriority);
|
|
}
|
|
return hooks;
|
|
}
|
|
/**
|
|
* Get all event types with registered hooks
|
|
*
|
|
* @returns Array of event types
|
|
*/
|
|
getEventTypes() {
|
|
return Array.from(this.hooks.keys());
|
|
}
|
|
/**
|
|
* Get count of hooks for an event
|
|
*
|
|
* @param event - Event type (optional)
|
|
* @returns Hook count
|
|
*/
|
|
count(event) {
|
|
if (event) {
|
|
return this.hooks.get(event)?.length || 0;
|
|
}
|
|
return this.hooksById.size;
|
|
}
|
|
/**
|
|
* Record hook execution statistics
|
|
*
|
|
* @param success - Whether execution succeeded
|
|
* @param executionTime - Execution time in ms
|
|
*/
|
|
recordExecution(success, executionTime) {
|
|
this.stats.executions++;
|
|
this.stats.totalExecutionTime += executionTime;
|
|
if (!success) {
|
|
this.stats.failures++;
|
|
}
|
|
}
|
|
/**
|
|
* Get hook statistics
|
|
*
|
|
* @returns Hook statistics
|
|
*/
|
|
getStats() {
|
|
const byEvent = {};
|
|
for (const [event, hooks] of this.hooks) {
|
|
byEvent[event] = hooks.filter(h => h.enabled).length;
|
|
}
|
|
return {
|
|
totalHooks: this.hooksById.size,
|
|
byEvent,
|
|
totalExecutions: this.stats.executions,
|
|
totalFailures: this.stats.failures,
|
|
avgExecutionTime: this.stats.executions > 0
|
|
? this.stats.totalExecutionTime / this.stats.executions
|
|
: 0,
|
|
totalExecutionTime: this.stats.totalExecutionTime,
|
|
};
|
|
}
|
|
/**
|
|
* Reset statistics
|
|
*/
|
|
resetStats() {
|
|
this.stats = {
|
|
executions: 0,
|
|
failures: 0,
|
|
totalExecutionTime: 0,
|
|
};
|
|
}
|
|
/**
|
|
* Check if a hook exists
|
|
*
|
|
* @param hookId - Hook ID
|
|
* @returns Whether hook exists
|
|
*/
|
|
has(hookId) {
|
|
return this.hooksById.has(hookId);
|
|
}
|
|
/**
|
|
* Clear all hooks and reset state
|
|
*/
|
|
clear() {
|
|
this.hooks.clear();
|
|
this.hooksById.clear();
|
|
this.hookIdCounter = 0;
|
|
this.resetStats();
|
|
}
|
|
}
|
|
/**
|
|
* Create a new hook registry
|
|
*/
|
|
export function createHookRegistry() {
|
|
return new HookRegistry();
|
|
}
|
|
//# sourceMappingURL=registry.js.map
|