modern decodators

This commit is contained in:
Boki 2025-06-21 21:24:09 -04:00
parent 8405f44bd9
commit 931f212ec7
6 changed files with 262 additions and 150 deletions

View file

@ -1,6 +1,7 @@
import { getLogger } from '@stock-bot/logger';
import type { IDataIngestionServices, IExecutionContext } from '@stock-bot/di';
import type { IHandler, ExecutionContext } from '../types/types';
import { handlerRegistry, createJobHandler, type HandlerConfigWithSchedule } from '@stock-bot/types';
/**
* Abstract base class for all handlers with improved DI
@ -8,9 +9,13 @@ import type { IHandler, ExecutionContext } from '../types/types';
*/
export abstract class BaseHandler implements IHandler {
protected readonly logger;
private handlerName: string;
constructor(protected readonly services: IDataIngestionServices) {
constructor(protected readonly services: IDataIngestionServices, 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
@ -20,18 +25,35 @@ export abstract class BaseHandler implements IHandler {
protected get queue() { return this.services.queue; }
/**
* Main execution method - must be implemented by subclasses
* Main execution method - automatically routes to decorated methods
* Works with queue (events commented for future)
*/
abstract execute(operation: string, input: unknown, context: ExecutionContext): Promise<unknown>;
async execute(operation: string, input: unknown, context: ExecutionContext): Promise<unknown> {
const constructor = this.constructor as any;
const operations = constructor.__operations || [];
// Find the operation metadata
const operationMeta = operations.find((op: any) => op.name === operation);
if (!operationMeta) {
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`);
}
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<void> {
const queue = this.services.queue.getQueue(this.constructor.name.toLowerCase());
const queue = this.services.queue.getQueue(this.handlerName);
const jobData = {
handler: this.constructor.name.toLowerCase(),
handler: this.handlerName,
operation,
payload
};
@ -58,6 +80,64 @@ export abstract class BaseHandler implements IHandler {
// 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<string, any> = {};
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.debug('Handler registered using decorator metadata', {
handlerName,
operations: operations.length,
schedules: schedules.length
});
}
/**
* 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
*/
@ -67,6 +147,7 @@ export abstract class BaseHandler implements IHandler {
async onDispose?(): Promise<void>;
}
/**
* Specialized handler for operations that have scheduled jobs
*/