diff --git a/libs/core/di/package.json b/libs/core/di/package.json index 5dda6f4..6311d2a 100644 --- a/libs/core/di/package.json +++ b/libs/core/di/package.json @@ -9,7 +9,8 @@ }, "dependencies": { "@stock-bot/config": "workspace:*", - "@stock-bot/logger": "workspace:*" + "@stock-bot/logger": "workspace:*", + "@stock-bot/handlers": "workspace:*" }, "devDependencies": { "@types/pg": "^8.10.7" diff --git a/libs/core/di/src/awilix-container.ts b/libs/core/di/src/awilix-container.ts index 168f00c..224b7cb 100644 --- a/libs/core/di/src/awilix-container.ts +++ b/libs/core/di/src/awilix-container.ts @@ -6,7 +6,7 @@ import type { Browser } from '@stock-bot/browser'; import type { CacheProvider } from '@stock-bot/cache'; import type { AppConfig as StockBotAppConfig } from '@stock-bot/config'; -import type { IServiceContainer } from '@stock-bot/handlers'; +import type { IServiceContainer } from '@stock-bot/types'; import type { Logger } from '@stock-bot/logger'; import type { MongoDBClient } from '@stock-bot/mongodb'; import type { PostgreSQLClient } from '@stock-bot/postgres'; diff --git a/libs/core/di/src/container/types.ts b/libs/core/di/src/container/types.ts index 8da34b0..70fe371 100644 --- a/libs/core/di/src/container/types.ts +++ b/libs/core/di/src/container/types.ts @@ -1,6 +1,6 @@ import type { Browser } from '@stock-bot/browser'; import type { CacheProvider } from '@stock-bot/cache'; -import type { IServiceContainer } from '@stock-bot/handlers'; +import type { IServiceContainer } from '@stock-bot/types'; import type { Logger } from '@stock-bot/logger'; import type { MongoDBClient } from '@stock-bot/mongodb'; import type { PostgreSQLClient } from '@stock-bot/postgres'; diff --git a/libs/core/di/src/service-application.ts b/libs/core/di/src/service-application.ts index a6e6a17..cdf165e 100644 --- a/libs/core/di/src/service-application.ts +++ b/libs/core/di/src/service-application.ts @@ -9,7 +9,7 @@ import { getLogger, setLoggerConfig, shutdownLoggers, type Logger } from '@stock import { Shutdown } from '@stock-bot/shutdown'; import type { AppConfig as StockBotAppConfig, UnifiedAppConfig } from '@stock-bot/config'; import { toUnifiedConfig } from '@stock-bot/config'; -import type { IServiceContainer } from '@stock-bot/handlers'; +import type { IServiceContainer } from '@stock-bot/types'; import type { ServiceContainer } from './awilix-container'; /** @@ -332,7 +332,7 @@ export class ServiceApplication { } this.logger.debug('Creating scheduled jobs from registered handlers...'); - const { handlerRegistry } = await import('@stock-bot/types'); + const { handlerRegistry } = await import('@stock-bot/handlers'); const allHandlers = handlerRegistry.getAllHandlersWithSchedule(); let totalScheduledJobs = 0; diff --git a/libs/core/handlers/package.json b/libs/core/handlers/package.json index 527cf0b..49edc1d 100644 --- a/libs/core/handlers/package.json +++ b/libs/core/handlers/package.json @@ -12,8 +12,7 @@ "dependencies": { "@stock-bot/config": "workspace:*", "@stock-bot/logger": "workspace:*", - "@stock-bot/types": "workspace:*", - "@stock-bot/di": "workspace:*" + "@stock-bot/types": "workspace:*" }, "devDependencies": { "@types/node": "^20.11.0", diff --git a/libs/core/handlers/src/base/BaseHandler.ts b/libs/core/handlers/src/base/BaseHandler.ts index c32f6c4..95af980 100644 --- a/libs/core/handlers/src/base/BaseHandler.ts +++ b/libs/core/handlers/src/base/BaseHandler.ts @@ -1,13 +1,11 @@ import type { Collection } from 'mongodb'; import { getLogger } from '@stock-bot/logger'; -import { - createJobHandler, - handlerRegistry, - type HandlerConfigWithSchedule, -} from '@stock-bot/types'; +import type { HandlerConfigWithSchedule } from '@stock-bot/types'; import { fetch } from '@stock-bot/utils'; import { createNamespacedCache } from '@stock-bot/cache'; -import type { IServiceContainer } from '../types/service-container'; +import type { IServiceContainer } from '@stock-bot/types'; +import { handlerRegistry } from '../registry/handler-registry'; +import { createJobHandler } from '../utils/create-job-handler'; import type { ExecutionContext, IHandler } from '../types/types'; /** diff --git a/libs/core/handlers/src/index.ts b/libs/core/handlers/src/index.ts index 0db14d2..f8bac94 100644 --- a/libs/core/handlers/src/index.ts +++ b/libs/core/handlers/src/index.ts @@ -2,8 +2,11 @@ export { BaseHandler, ScheduledHandler } from './base/BaseHandler'; export type { JobScheduleOptions } from './base/BaseHandler'; -// Handler registry (re-exported from types to avoid circular deps) -export { handlerRegistry } from '@stock-bot/types'; +// Handler registry +export { handlerRegistry } from './registry/handler-registry'; + +// Utilities +export { createJobHandler } from './utils/create-job-handler'; // Types export type { @@ -18,9 +21,8 @@ export type { OperationMetadata, } from './types/types'; -export type { IServiceContainer } from './types/service-container'; - -export { createJobHandler } from './types/types'; +// Re-export IServiceContainer from types package +export type { IServiceContainer } from '@stock-bot/types'; // Decorators export { diff --git a/libs/core/handlers/src/registry/auto-register.ts b/libs/core/handlers/src/registry/auto-register.ts index a98e31a..fa684f0 100644 --- a/libs/core/handlers/src/registry/auto-register.ts +++ b/libs/core/handlers/src/registry/auto-register.ts @@ -7,7 +7,7 @@ import { readdirSync, statSync } from 'fs'; import { join, relative } from 'path'; import { getLogger } from '@stock-bot/logger'; import { BaseHandler } from '../base/BaseHandler'; -import type { IServiceContainer } from '../types/service-container'; +import type { IServiceContainer } from '@stock-bot/types'; const logger = getLogger('handler-auto-register'); diff --git a/libs/core/types/src/handler-registry.ts b/libs/core/handlers/src/registry/handler-registry.ts similarity index 77% rename from libs/core/types/src/handler-registry.ts rename to libs/core/handlers/src/registry/handler-registry.ts index b300431..43bfbb2 100644 --- a/libs/core/types/src/handler-registry.ts +++ b/libs/core/handlers/src/registry/handler-registry.ts @@ -1,121 +1,143 @@ -/** - * Handler Registry - Lightweight registry for queue handlers - * Moved here to avoid circular dependencies between handlers and queue - */ - -import type { - HandlerConfig, - HandlerConfigWithSchedule, - JobHandler, - ScheduledJob, -} from './handlers'; -import { getLogger } from '@stock-bot/logger'; - -class HandlerRegistry { - private readonly logger = getLogger('handler-registry'); - private handlers = new Map(); - private handlerSchedules = new Map(); - - /** - * Register a handler with its operations (simple config) - */ - register(handlerName: string, config: HandlerConfig): void { - this.logger.info(`Registering handler: ${handlerName}`, { - operations: Object.keys(config), - }); - - this.handlers.set(handlerName, config); - } - - /** - * Register a handler with scheduled jobs (enhanced config) - */ - registerWithSchedule(config: HandlerConfigWithSchedule): void { - this.logger.info(`Registering handler with schedule: ${config.name}`, { - operations: Object.keys(config.operations), - scheduledJobs: config.scheduledJobs?.length || 0, - }); - - this.handlers.set(config.name, config.operations); - - if (config.scheduledJobs && config.scheduledJobs.length > 0) { - this.handlerSchedules.set(config.name, config.scheduledJobs); - } - } - - /** - * Get a specific handler's configuration - */ - getHandler(handlerName: string): HandlerConfig | undefined { - return this.handlers.get(handlerName); - } - - /** - * Get all registered handlers - */ - getAllHandlers(): Map { - return new Map(this.handlers); - } - - /** - * Get scheduled jobs for a handler - */ - getScheduledJobs(handlerName: string): ScheduledJob[] { - return this.handlerSchedules.get(handlerName) || []; - } - - /** - * Get all handlers with their scheduled jobs - */ - getAllHandlersWithSchedule(): Map< - string, - { operations: HandlerConfig; scheduledJobs: ScheduledJob[] } - > { - const result = new Map(); - - for (const [name, operations] of this.handlers) { - result.set(name, { - operations, - scheduledJobs: this.handlerSchedules.get(name) || [], - }); - } - - return result; - } - - /** - * Get a specific operation from a handler - */ - getOperation(handlerName: string, operationName: string): JobHandler | undefined { - const handler = this.handlers.get(handlerName); - if (!handler) { - return undefined; - } - return handler[operationName]; - } - - /** - * Check if a handler is registered - */ - hasHandler(handlerName: string): boolean { - return this.handlers.has(handlerName); - } - - /** - * Get list of all registered handler names - */ - getHandlerNames(): string[] { - return Array.from(this.handlers.keys()); - } - - /** - * Clear all registrations (useful for testing) - */ - clear(): void { - this.handlers.clear(); - this.handlerSchedules.clear(); - } -} - -// Export singleton instance -export const handlerRegistry = new HandlerRegistry(); +/** + * Handler Registry - Runtime registry for queue handlers + * Properly located in handlers package instead of types + */ + +import type { + HandlerConfig, + HandlerConfigWithSchedule, + JobHandler, + ScheduledJob, +} from '@stock-bot/types'; +import { getLogger } from '@stock-bot/logger'; + +class HandlerRegistry { + private readonly logger = getLogger('handler-registry'); + private handlers = new Map(); + private handlerSchedules = new Map(); + + /** + * Register a handler with its operations (simple config) + */ + register(handlerName: string, config: HandlerConfig): void { + this.logger.info(`Registering handler: ${handlerName}`, { + operations: Object.keys(config), + }); + + this.handlers.set(handlerName, config); + } + + /** + * Register a handler with scheduled jobs (enhanced config) + */ + registerWithSchedule(config: HandlerConfigWithSchedule): void { + this.logger.info(`Registering handler with schedule: ${config.name}`, { + operations: Object.keys(config.operations), + scheduledJobs: config.scheduledJobs?.length || 0, + }); + + this.handlers.set(config.name, config.operations); + + if (config.scheduledJobs && config.scheduledJobs.length > 0) { + this.handlerSchedules.set(config.name, config.scheduledJobs); + } + } + + /** + * Get a specific handler's configuration + */ + getHandler(handlerName: string): HandlerConfig | undefined { + return this.handlers.get(handlerName); + } + + /** + * Get all registered handlers + */ + getAllHandlers(): Map { + return new Map(this.handlers); + } + + /** + * Get scheduled jobs for a handler + */ + getScheduledJobs(handlerName: string): ScheduledJob[] { + return this.handlerSchedules.get(handlerName) || []; + } + + /** + * Get all handlers with their scheduled jobs + */ + getAllHandlersWithSchedule(): Map< + string, + { operations: HandlerConfig; scheduledJobs: ScheduledJob[] } + > { + const result = new Map(); + + for (const [name, operations] of this.handlers) { + result.set(name, { + operations, + scheduledJobs: this.handlerSchedules.get(name) || [], + }); + } + + return result; + } + + /** + * Get a specific operation from a handler + */ + getOperation(handlerName: string, operationName: string): JobHandler | undefined { + const handler = this.handlers.get(handlerName); + if (!handler) { + return undefined; + } + return handler[operationName]; + } + + /** + * Check if a handler is registered + */ + hasHandler(handlerName: string): boolean { + return this.handlers.has(handlerName); + } + + /** + * Get list of all registered handler names + */ + getHandlerNames(): string[] { + return Array.from(this.handlers.keys()); + } + + /** + * Get registry statistics + */ + getStats(): { handlers: number; operations: number; scheduledJobs: number } { + let operationCount = 0; + let scheduledJobCount = 0; + + for (const [_, config] of this.handlers) { + operationCount += Object.keys(config).length; + } + + for (const [_, jobs] of this.handlerSchedules) { + scheduledJobCount += jobs.length; + } + + return { + handlers: this.handlers.size, + operations: operationCount, + scheduledJobs: scheduledJobCount, + }; + } + + /** + * Clear all registrations (useful for testing) + */ + clear(): void { + this.handlers.clear(); + this.handlerSchedules.clear(); + } +} + +// Export singleton instance +export const handlerRegistry = new HandlerRegistry(); \ No newline at end of file diff --git a/libs/core/handlers/src/types/types.ts b/libs/core/handlers/src/types/types.ts index d87e571..0cb2562 100644 --- a/libs/core/handlers/src/types/types.ts +++ b/libs/core/handlers/src/types/types.ts @@ -10,5 +10,3 @@ export type { ScheduledJob, TypedJobHandler, } from '@stock-bot/types'; - -export { createJobHandler } from '@stock-bot/types'; diff --git a/libs/core/handlers/src/utils/create-job-handler.ts b/libs/core/handlers/src/utils/create-job-handler.ts new file mode 100644 index 0000000..7f5012e --- /dev/null +++ b/libs/core/handlers/src/utils/create-job-handler.ts @@ -0,0 +1,16 @@ +/** + * Utility for creating typed job handlers + */ + +import type { JobHandler, TypedJobHandler } from '@stock-bot/types'; + +/** + * Create a typed job handler with validation + */ +export function createJobHandler( + handler: TypedJobHandler +): JobHandler { + return async (payload: unknown): Promise => { + return handler(payload as TPayload); + }; +} \ No newline at end of file diff --git a/libs/core/types/src/handlers.ts b/libs/core/types/src/handlers.ts index 6022e72..9985efc 100644 --- a/libs/core/types/src/handlers.ts +++ b/libs/core/types/src/handlers.ts @@ -71,13 +71,3 @@ export interface OperationMetadata { validation?: (input: unknown) => boolean; } -/** - * Create a typed job handler with validation - */ -export function createJobHandler( - handler: TypedJobHandler -): JobHandler { - return async (payload: unknown): Promise => { - return handler(payload as TPayload); - }; -} diff --git a/libs/core/types/src/index.ts b/libs/core/types/src/index.ts index 237fc0a..c90cfce 100644 --- a/libs/core/types/src/index.ts +++ b/libs/core/types/src/index.ts @@ -60,7 +60,7 @@ export type { ScheduledJob, TypedJobHandler, } from './handlers'; -export { createJobHandler } from './handlers'; -// Export handler registry -export { handlerRegistry } from './handler-registry'; + +// Export service container types +export type { IServiceContainer } from './service-container'; diff --git a/libs/core/handlers/src/types/service-container.ts b/libs/core/types/src/service-container.ts similarity index 86% rename from libs/core/handlers/src/types/service-container.ts rename to libs/core/types/src/service-container.ts index adac159..135ce2f 100644 --- a/libs/core/handlers/src/types/service-container.ts +++ b/libs/core/types/src/service-container.ts @@ -1,28 +1,26 @@ -/** - * Universal Service Container for Handlers - * Simple, comprehensive container with all services available - */ - -import type { ProxyManager } from '@stock-bot/proxy'; - -/** - * Universal service container with all common services - * Designed to work across different service contexts (data-ingestion, processing, etc.) - */ -export interface IServiceContainer { - // Core infrastructure - readonly logger: any; // Logger instance - readonly cache?: any; // Cache provider (Redis/Dragonfly) - optional - readonly globalCache?: any; // Global cache provider (shared across services) - optional - readonly queue?: any; // Queue manager (BullMQ) - optional - readonly proxy?: ProxyManager; // Proxy manager service - optional (depends on cache) - readonly browser?: any; // Browser automation (Playwright) - - // Database clients - all optional to support selective enabling - readonly mongodb?: any; // MongoDB client - readonly postgres?: any; // PostgreSQL client - readonly questdb?: any; // QuestDB client (time-series) - - // Optional extensions for future use - readonly custom?: Record; -} +/** + * Universal Service Container for Handlers + * Simple, comprehensive container with all services available + */ + +/** + * Universal service container with all common services + * Designed to work across different service contexts (data-ingestion, processing, etc.) + */ +export interface IServiceContainer { + // Core infrastructure + readonly logger: any; // Logger instance + readonly cache?: any; // Cache provider (Redis/Dragonfly) - optional + readonly globalCache?: any; // Global cache provider (shared across services) - optional + readonly queue?: any; // Queue manager (BullMQ) - optional + readonly proxy?: any; // Proxy manager service - optional (depends on cache) + readonly browser?: any; // Browser automation (Playwright) + + // Database clients - all optional to support selective enabling + readonly mongodb?: any; // MongoDB client + readonly postgres?: any; // PostgreSQL client + readonly questdb?: any; // QuestDB client (time-series) + + // Optional extensions for future use + readonly custom?: Record; +} \ No newline at end of file diff --git a/libs/services/queue/package.json b/libs/services/queue/package.json index 5f10b43..db2c01e 100644 --- a/libs/services/queue/package.json +++ b/libs/services/queue/package.json @@ -15,7 +15,8 @@ "rate-limiter-flexible": "^3.0.0", "@stock-bot/cache": "*", "@stock-bot/logger": "*", - "@stock-bot/types": "*" + "@stock-bot/types": "*", + "@stock-bot/handlers": "*" }, "devDependencies": { "typescript": "^5.3.0", diff --git a/libs/services/queue/src/index.ts b/libs/services/queue/src/index.ts index e1e2ae7..70111e2 100644 --- a/libs/services/queue/src/index.ts +++ b/libs/services/queue/src/index.ts @@ -2,7 +2,6 @@ export { Queue, type QueueWorkerConfig } from './queue'; export { QueueManager } from './queue-manager'; export { SmartQueueManager } from './smart-queue-manager'; -export { createJobHandler } from './types'; export { ServiceCache, createServiceCache } from './service-cache'; export { SERVICE_REGISTRY, @@ -12,8 +11,8 @@ export { parseQueueName } from './service-registry'; -// Re-export handler registry from types package -export { handlerRegistry } from '@stock-bot/types'; +// Re-export handler registry and utilities from handlers package +export { handlerRegistry, createJobHandler } from '@stock-bot/handlers'; // Batch processing export { processBatchJob, processItems } from './batch-processor'; diff --git a/libs/services/queue/src/queue.ts b/libs/services/queue/src/queue.ts index e40d553..479f8bd 100644 --- a/libs/services/queue/src/queue.ts +++ b/libs/services/queue/src/queue.ts @@ -1,5 +1,5 @@ import { Queue as BullQueue, QueueEvents, Worker, type Job } from 'bullmq'; -import { handlerRegistry } from '@stock-bot/types'; +import { handlerRegistry } from '@stock-bot/handlers'; import type { JobData, JobOptions, QueueStats, RedisConfig } from './types'; import { getRedisConnection } from './utils'; diff --git a/libs/services/queue/src/smart-queue-manager.ts b/libs/services/queue/src/smart-queue-manager.ts index 0d85bbb..052bb67 100644 --- a/libs/services/queue/src/smart-queue-manager.ts +++ b/libs/services/queue/src/smart-queue-manager.ts @@ -1,5 +1,5 @@ import { Queue as BullQueue, type Job } from 'bullmq'; -import { handlerRegistry } from '@stock-bot/types'; +import { handlerRegistry } from '@stock-bot/handlers'; import { getLogger, type Logger } from '@stock-bot/logger'; import { QueueManager } from './queue-manager'; import { Queue } from './queue'; diff --git a/libs/services/queue/src/types.ts b/libs/services/queue/src/types.ts index 9b94bce..87f3e4e 100644 --- a/libs/services/queue/src/types.ts +++ b/libs/services/queue/src/types.ts @@ -107,7 +107,6 @@ export interface QueueConfig extends QueueManagerConfig { enableMetrics?: boolean; } -export { createJobHandler } from '@stock-bot/types'; export interface BatchJobData { payloadKey: string; diff --git a/scripts/build-libs.sh b/scripts/build-libs.sh index 16a61a6..df0c3e4 100755 --- a/scripts/build-libs.sh +++ b/scripts/build-libs.sh @@ -42,22 +42,21 @@ libs=( "data/postgres" # PostgreSQL client - depends on core libs "data/questdb" # QuestDB client - depends on core libs + # Core handlers - must be built before services that depend on it + "core/handlers" # Handlers - depends on core libs + # Service libraries "services/event-bus" # Event bus - depends on core libs "services/shutdown" # Shutdown - depends on core libs "services/browser" # Browser - depends on core libs - "services/queue" # Queue - depends on core libs and cache + "services/queue" # Queue - depends on core libs, cache, and handlers "services/proxy" # Proxy manager - depends on core libs and cache # Utils "utils" # Utilities - depends on many libs - # DI - dependency injection library - "core/di" # Dependency injection - depends on data and service libs - "core/handlers" # Handlers - depends on core libs and utils - - # Note: core/handlers is not included in lib build chain since no libs depend on it - # It's built separately when needed by applications + # DI - dependency injection library + "core/di" # Dependency injection - depends on data, service libs, and handlers ) # Build each library in order