import { getLogger } from '@stock-bot/logger'; import type { IHandler, ExecutionContext } from '../types/types'; import type { IServiceContainer } from '../types/service-container'; import { handlerRegistry, createJobHandler, type HandlerConfigWithSchedule } from '@stock-bot/types'; /** * Abstract base class for all handlers with improved DI * Provides common functionality and structure for queue/event operations */ export abstract class BaseHandler implements IHandler { protected readonly logger; private handlerName: string; constructor(protected readonly services: IServiceContainer, handlerName?: string) { this.logger = getLogger(this.constructor.name); // Read handler name from decorator first, then fallback to parameter or class name const constructor = this.constructor as any; this.handlerName = constructor.__handlerName || handlerName || this.constructor.name.toLowerCase(); } // Convenience getters for common services protected get mongodb() { return this.services.mongodb; } protected get postgres() { return this.services.postgres; } protected get questdb() { return this.services.questdb; } protected get cache() { return this.services.cache; } protected get queue() { return this.services.queue; } protected get http() { return this.services.http; } /** * Main execution method - automatically routes to decorated methods * Works with queue (events commented for future) */ async execute(operation: string, input: unknown, context: ExecutionContext): Promise { const constructor = this.constructor as any; const operations = constructor.__operations || []; // Debug logging this.logger.debug('Handler execute called', { handler: this.handlerName, operation, availableOperations: operations.map((op: any) => ({ name: op.name, method: op.method })) }); // Find the operation metadata const operationMeta = operations.find((op: any) => op.name === operation); if (!operationMeta) { this.logger.error('Operation not found', { requestedOperation: operation, availableOperations: operations.map((op: any) => op.name) }); throw new Error(`Unknown operation: ${operation}`); } // Get the method from the instance and call it const method = (this as any)[operationMeta.method]; if (typeof method !== 'function') { throw new Error(`Operation method '${operationMeta.method}' not found on handler`); } this.logger.debug('Executing operation method', { operation, method: operationMeta.method }); return await method.call(this, input, context); } /** * Queue helper methods - now type-safe and direct */ protected async scheduleOperation(operation: string, payload: unknown, delay?: number): Promise { const queue = this.services.queue.getQueue(this.handlerName); const jobData = { handler: this.handlerName, operation, payload }; await queue.add(operation, jobData, { delay }); } /** * Create execution context for operations */ protected createExecutionContext(type: 'http' | 'queue' | 'scheduled', metadata: Record = {}): ExecutionContext { return { type, metadata: { ...metadata, timestamp: Date.now(), traceId: `${this.constructor.name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` } }; } /** * Event methods - commented for future */ // protected async publishEvent(eventName: string, payload: unknown): Promise { // const eventBus = await this.container.resolveAsync('eventBus'); // await eventBus.publish(eventName, payload); // } /** * Register this handler using decorator metadata * Automatically reads @Handler, @Operation, and @QueueSchedule decorators */ register(): void { const constructor = this.constructor as any; const handlerName = constructor.__handlerName || this.handlerName; const operations = constructor.__operations || []; const schedules = constructor.__schedules || []; // Create operation handlers from decorator metadata const operationHandlers: Record = {}; for (const op of operations) { operationHandlers[op.name] = createJobHandler(async (payload) => { const context: ExecutionContext = { type: 'queue', metadata: { source: 'queue', timestamp: Date.now() } }; return await this.execute(op.name, payload, context); }); } // Create scheduled jobs from decorator metadata const scheduledJobs = schedules.map((schedule: any) => { // Find the operation name from the method name const operation = operations.find((op: any) => op.method === schedule.operation); return { type: `${handlerName}-${schedule.operation}`, operation: operation?.name || schedule.operation, cronPattern: schedule.cronPattern, priority: schedule.priority || 5, immediately: schedule.immediately || false, description: schedule.description || `${handlerName} ${schedule.operation}`, payload: this.getScheduledJobPayload?.(schedule.operation), }; }); const config: HandlerConfigWithSchedule = { name: handlerName, operations: operationHandlers, scheduledJobs, }; handlerRegistry.registerWithSchedule(config); this.logger.info('Handler registered using decorator metadata', { handlerName, operations: operations.map((op: any) => ({ name: op.name, method: op.method })), scheduledJobs: scheduledJobs.map((job: any) => ({ operation: job.operation, cronPattern: job.cronPattern, immediately: job.immediately })) }); } /** * Override this method to provide payloads for scheduled jobs * @param operation The operation name that needs a payload * @returns The payload for the scheduled job, or undefined */ protected getScheduledJobPayload?(operation: string): any; /** * Lifecycle hooks - can be overridden by subclasses */ async onInit?(): Promise; async onStart?(): Promise; async onStop?(): Promise; async onDispose?(): Promise; } /** * Specialized handler for operations that have scheduled jobs */ export abstract class ScheduledHandler extends BaseHandler { /** * Get scheduled job configurations for this handler * Override in subclasses to define schedules */ getScheduledJobs?(): Array<{ operation: string; cronPattern: string; priority?: number; immediately?: boolean; description?: string; }>; }