/** * Auto-registration utilities for handlers * Automatically discovers and registers handlers based on file patterns */ import { getLogger } from '@stock-bot/logger'; import type { IServiceContainer } from '../types/service-container'; import { BaseHandler } from '../base/BaseHandler'; import { readdirSync, statSync } from 'fs'; import { join, relative } from 'path'; const logger = getLogger('handler-auto-register'); /** * Recursively find all handler files in a directory */ function findHandlerFiles(dir: string, pattern = '.handler.'): string[] { const files: string[] = []; function scan(currentDir: string) { const entries = readdirSync(currentDir); for (const entry of entries) { const fullPath = join(currentDir, entry); const stat = statSync(fullPath); if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') { scan(fullPath); } else if (stat.isFile() && entry.includes(pattern) && entry.endsWith('.ts')) { files.push(fullPath); } } } scan(dir); return files; } /** * Extract handler classes from a module */ function extractHandlerClasses(module: any): Array BaseHandler> { const handlers: Array BaseHandler> = []; for (const key of Object.keys(module)) { const exported = module[key]; // Check if it's a class that extends BaseHandler if ( typeof exported === 'function' && exported.prototype && exported.prototype instanceof BaseHandler ) { handlers.push(exported); } } return handlers; } /** * Auto-register all handlers in a directory * @param directory The directory to scan for handlers * @param services The service container to inject into handlers * @param options Configuration options */ export async function autoRegisterHandlers( directory: string, services: IServiceContainer, options: { pattern?: string; exclude?: string[]; dryRun?: boolean; } = {} ): Promise<{ registered: string[]; failed: string[] }> { const { pattern = '.handler.', exclude = [], dryRun = false } = options; const registered: string[] = []; const failed: string[] = []; try { logger.info('Starting auto-registration of handlers', { directory, pattern }); // Find all handler files const handlerFiles = findHandlerFiles(directory, pattern); logger.debug(`Found ${handlerFiles.length} handler files`, { files: handlerFiles }); // Process each handler file for (const file of handlerFiles) { const relativePath = relative(directory, file); // Skip excluded files if (exclude.some(ex => relativePath.includes(ex))) { logger.debug(`Skipping excluded file: ${relativePath}`); continue; } try { // Import the module const module = await import(file); const handlerClasses = extractHandlerClasses(module); if (handlerClasses.length === 0) { logger.warn(`No handler classes found in ${relativePath}`); continue; } // Register each handler class for (const HandlerClass of handlerClasses) { const handlerName = HandlerClass.name; if (dryRun) { logger.info(`[DRY RUN] Would register handler: ${handlerName} from ${relativePath}`); registered.push(handlerName); } else { logger.info(`Registering handler: ${handlerName} from ${relativePath}`); // Create instance and register const handler = new HandlerClass(services); handler.register(); registered.push(handlerName); logger.info(`Successfully registered handler: ${handlerName}`); } } } catch (error) { logger.error(`Failed to process handler file: ${relativePath}`, { error }); failed.push(relativePath); } } logger.info('Auto-registration complete', { totalFiles: handlerFiles.length, registered: registered.length, failed: failed.length }); return { registered, failed }; } catch (error) { logger.error('Auto-registration failed', { error }); throw error; } } /** * Create a handler registry that auto-discovers handlers */ export function createAutoHandlerRegistry(services: IServiceContainer) { return { /** * Register all handlers from a directory */ async registerDirectory(directory: string, options?: Parameters[2]) { return autoRegisterHandlers(directory, services, options); }, /** * Register handlers from multiple directories */ async registerDirectories(directories: string[], options?: Parameters[2]) { const results = { registered: [] as string[], failed: [] as string[] }; for (const dir of directories) { const result = await autoRegisterHandlers(dir, services, options); results.registered.push(...result.registered); results.failed.push(...result.failed); } return results; } }; }