huge refactor to remove depenencie hell and add typesafe container

This commit is contained in:
Boki 2025-06-24 09:37:51 -04:00
parent 28b9822d55
commit 843a7b9b9b
148 changed files with 3603 additions and 2378 deletions

View file

@ -1,14 +1,17 @@
import type { Collection } from 'mongodb';
import { createNamespacedCache } from '@stock-bot/cache';
import { getLogger } from '@stock-bot/logger';
import type {
HandlerConfigWithSchedule,
IServiceContainer,
import type {
ExecutionContext,
IHandler
HandlerConfigWithSchedule,
HandlerMetadata,
IHandler,
IServiceContainer,
JobHandler,
ServiceTypes,
} from '@stock-bot/types';
import { fetch } from '@stock-bot/utils';
import { createNamespacedCache } from '@stock-bot/cache';
import { handlerRegistry } from '../registry/handler-registry';
// Handler registry is now injected, not imported
import { createJobHandler } from '../utils/create-job-handler';
/**
@ -38,16 +41,16 @@ export interface JobScheduleOptions {
* Provides common functionality and structure for queue/event operations
*/
export abstract class BaseHandler implements IHandler {
// Direct service properties - flattened for cleaner access
readonly logger;
readonly cache;
readonly globalCache;
readonly queue;
readonly proxy;
readonly browser;
readonly mongodb;
readonly postgres;
readonly questdb;
// Direct service properties - flattened for cleaner access with proper types
readonly logger: ServiceTypes['logger'];
readonly cache: ServiceTypes['cache'];
readonly globalCache: ServiceTypes['globalCache'];
readonly queue: ServiceTypes['queue'];
readonly proxy: ServiceTypes['proxy'];
readonly browser: ServiceTypes['browser'];
readonly mongodb: ServiceTypes['mongodb'];
readonly postgres: ServiceTypes['postgres'];
readonly questdb: ServiceTypes['questdb'];
private handlerName: string;
@ -109,8 +112,8 @@ export abstract class BaseHandler implements IHandler {
}
async scheduleOperation(
operation: string,
payload: unknown,
operation: string,
payload: unknown,
options?: JobScheduleOptions
): Promise<void> {
if (!this.queue) {
@ -122,7 +125,7 @@ export abstract class BaseHandler implements IHandler {
operation,
payload,
};
await queue.add(operation, jobData, options || {});
}
@ -162,7 +165,7 @@ export abstract class BaseHandler implements IHandler {
* Example: handler 'webshare' creates namespace 'webshare:api' -> keys will be 'cache:data-ingestion:webshare:api:*'
*/
protected createNamespacedCache(subNamespace: string) {
return createNamespacedCache(this.cache, `${this.handlerName}:${subNamespace}`);
return createNamespacedCache(this.cache || null, `${this.handlerName}:${subNamespace}`);
}
/**
@ -197,36 +200,36 @@ export abstract class BaseHandler implements IHandler {
// Don't add 'cache:' prefix since the cache already has its own prefix
return this.cache.del(`${this.handlerName}:${key}`);
}
/**
* Set global cache with key
*/
protected async globalCacheSet(key: string, value: any, ttl?: number): Promise<void> {
if (!this.globalCache) {
return;
}
return this.globalCache.set(key, value, ttl);
/**
* Set global cache with key
*/
protected async globalCacheSet(key: string, value: any, ttl?: number): Promise<void> {
if (!this.globalCache) {
return;
}
/**
* Get global cache with key
*/
protected async globalCacheGet<T = any>(key: string): Promise<T | null> {
if (!this.globalCache) {
return null;
}
return this.globalCache.get(key);
return this.globalCache.set(key, value, ttl);
}
/**
* Get global cache with key
*/
protected async globalCacheGet<T = any>(key: string): Promise<T | null> {
if (!this.globalCache) {
return null;
}
/**
* Delete global cache with key
*/
protected async globalCacheDel(key: string): Promise<void> {
if (!this.globalCache) {
return;
}
return this.globalCache.del(key);
return this.globalCache.get(key);
}
/**
* Delete global cache with key
*/
protected async globalCacheDel(key: string): Promise<void> {
if (!this.globalCache) {
return;
}
return this.globalCache.del(key);
}
/**
* Schedule operation with delay in seconds
*/
@ -238,7 +241,7 @@ export abstract class BaseHandler implements IHandler {
): Promise<void> {
return this.scheduleOperation(operation, payload, {
delay: delaySeconds * 1000,
...additionalOptions
...additionalOptions,
});
}
@ -294,27 +297,45 @@ export abstract class BaseHandler implements IHandler {
// }
/**
* Register this handler using decorator metadata
* Automatically reads @Handler, @Operation, and @QueueSchedule decorators
* Create handler configuration with job handlers
* This is used by the scanner to create the actual handler configuration
*/
register(serviceName?: string): void {
const constructor = this.constructor as any;
const handlerName = constructor.__handlerName || this.handlerName;
const operations = constructor.__operations || [];
const schedules = constructor.__schedules || [];
createHandlerConfig(): HandlerConfigWithSchedule {
const metadata = (this.constructor as typeof BaseHandler).extractMetadata();
if (!metadata) {
throw new Error('Handler metadata not found');
}
// Create operation handlers from decorator metadata
const operationHandlers: Record<string, any> = {};
for (const op of operations) {
operationHandlers[op.name] = createJobHandler(async payload => {
const operationHandlers: Record<string, JobHandler> = {};
for (const opName of metadata.operations) {
operationHandlers[opName] = createJobHandler(async (payload: any) => {
const context: ExecutionContext = {
type: 'queue',
metadata: { source: 'queue', timestamp: Date.now() },
};
return await this.execute(op.name, payload, context);
return await this.execute(opName, payload, context);
});
}
return {
name: metadata.name,
operations: operationHandlers,
scheduledJobs: metadata.scheduledJobs,
};
}
/**
* Extract handler metadata from decorators
* This returns metadata only - actual handler instances are created by the scanner
*/
static extractMetadata(): HandlerMetadata | null {
const constructor = this as any;
const handlerName = constructor.__handlerName;
if (!handlerName) return null;
const operations = constructor.__operations || [];
const schedules = constructor.__schedules || [];
// Create scheduled jobs from decorator metadata
const scheduledJobs = schedules.map((schedule: any) => {
// Find the operation name from the method name
@ -326,27 +347,15 @@ export abstract class BaseHandler implements IHandler {
priority: schedule.priority || 5,
immediately: schedule.immediately || false,
description: schedule.description || `${handlerName} ${schedule.operation}`,
payload: this.getScheduledJobPayload?.(schedule.operation),
};
});
const config: HandlerConfigWithSchedule = {
return {
name: handlerName,
operations: operationHandlers,
operations: operations.map((op: any) => op.name),
scheduledJobs,
description: constructor.__description,
};
handlerRegistry.registerWithSchedule(config, serviceName);
this.logger.info('Handler registered using decorator metadata', {
handlerName,
service: serviceName,
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,
})),
});
}
/**