diff --git a/.env b/.env index 77fea79..a029ae7 100644 --- a/.env +++ b/.env @@ -4,8 +4,8 @@ # Core Application Settings NODE_ENV=development -LOG_LEVEL=debug -LOG_HIDE_OBJECT=false +LOG_LEVEL=trace +LOG_HIDE_OBJECT=true # Data Service Configuration DATA_SERVICE_PORT=2001 diff --git a/libs/core/config/src/config-manager.ts b/libs/core/config/src/config-manager.ts index c2449a9..ed30d3d 100644 --- a/libs/core/config/src/config-manager.ts +++ b/libs/core/config/src/config-manager.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import { EnvLoader } from './loaders/env.loader'; import { FileLoader } from './loaders/file.loader'; import { ConfigError, ConfigValidationError } from './errors'; -import { +import type { ConfigLoader, ConfigManagerOptions, ConfigSchema, diff --git a/libs/core/config/src/index.ts b/libs/core/config/src/index.ts index cd65a71..25594d3 100644 --- a/libs/core/config/src/index.ts +++ b/libs/core/config/src/index.ts @@ -2,7 +2,8 @@ import { EnvLoader } from './loaders/env.loader'; import { FileLoader } from './loaders/file.loader'; import { ConfigManager } from './config-manager'; -import { AppConfig, appConfigSchema } from './schemas'; +import type { AppConfig } from './schemas'; +import { appConfigSchema } from './schemas'; // Create singleton instance let configInstance: ConfigManager | null = null; diff --git a/libs/core/config/src/loaders/env.loader.ts b/libs/core/config/src/loaders/env.loader.ts index 3e703f8..2da2cb1 100644 --- a/libs/core/config/src/loaders/env.loader.ts +++ b/libs/core/config/src/loaders/env.loader.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; import { ConfigLoaderError } from '../errors'; -import { ConfigLoader } from '../types'; +import type { ConfigLoader } from '../types'; export interface EnvLoaderOptions { convertCase?: boolean; diff --git a/libs/core/config/src/loaders/file.loader.ts b/libs/core/config/src/loaders/file.loader.ts index 0306054..ed3cdf6 100644 --- a/libs/core/config/src/loaders/file.loader.ts +++ b/libs/core/config/src/loaders/file.loader.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { ConfigLoaderError } from '../errors'; -import { ConfigLoader } from '../types'; +import type { ConfigLoader } from '../types'; export class FileLoader implements ConfigLoader { readonly priority = 50; // Medium priority diff --git a/libs/core/config/src/schemas/database.schema.ts b/libs/core/config/src/schemas/database.schema.ts index 88e027b..f4d1aff 100644 --- a/libs/core/config/src/schemas/database.schema.ts +++ b/libs/core/config/src/schemas/database.schema.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; // PostgreSQL configuration export const postgresConfigSchema = z.object({ + enabled: z.boolean().default(true), host: z.string().default('localhost'), port: z.number().default(5432), database: z.string(), @@ -15,6 +16,7 @@ export const postgresConfigSchema = z.object({ // QuestDB configuration export const questdbConfigSchema = z.object({ + enabled: z.boolean().default(true), host: z.string().default('localhost'), ilpPort: z.number().default(9009), httpPort: z.number().default(9000), @@ -28,6 +30,7 @@ export const questdbConfigSchema = z.object({ // MongoDB configuration export const mongodbConfigSchema = z.object({ + enabled: z.boolean().default(true), uri: z.string().url().optional(), host: z.string().default('localhost'), port: z.number().default(27017), @@ -41,6 +44,7 @@ export const mongodbConfigSchema = z.object({ // Dragonfly/Redis configuration export const dragonflyConfigSchema = z.object({ + enabled: z.boolean().default(true), host: z.string().default('localhost'), port: z.number().default(6379), password: z.string().optional(), diff --git a/libs/core/di/src/awilix-container.ts b/libs/core/di/src/awilix-container.ts index 1c247ae..1f67a55 100644 --- a/libs/core/di/src/awilix-container.ts +++ b/libs/core/di/src/awilix-container.ts @@ -16,6 +16,7 @@ import { asFunction, asValue, createContainer, InjectionMode, type AwilixContain // Configuration types export interface AppConfig { redis: { + enabled?: boolean; host: string; port: number; password?: string; @@ -23,10 +24,12 @@ export interface AppConfig { db?: number; }; mongodb: { + enabled?: boolean; uri: string; database: string; }; postgres: { + enabled?: boolean; host: string; port: number; database: string; @@ -34,6 +37,7 @@ export interface AppConfig { password: string; }; questdb?: { + enabled?: boolean; host: string; httpPort?: number; pgPort?: number; @@ -59,7 +63,7 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { }); // Register configuration values - container.register({ + const registrations: any = { // Configuration config: asValue(config), redisConfig: asValue(config.redis), @@ -69,9 +73,11 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { // Core services with dependency injection logger: asFunction(() => getLogger('app')).singleton(), - - // Cache with injected config and logger - cache: asFunction(({ redisConfig, logger }) => + }; + + // Conditionally register cache/dragonfly + if (config.redis?.enabled !== false) { + registrations.cache = asFunction(({ redisConfig, logger }) => createCache({ redisConfig, logger, @@ -79,23 +85,37 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { ttl: 3600, enableMetrics: true, }) - ).singleton(), - - // Proxy manager with injected cache and logger - proxyManager: asFunction(({ cache, config, logger }) => { - const manager = new ProxyManager( - cache, - config.proxy || {}, - logger - ); - // Note: initialization happens in initializeServices function - return manager; - }).singleton(), // MongoDB client with injected dependencies - mongoClient: asFunction(({ mongoConfig, logger }) => { + ).singleton(); + } else { + registrations.cache = asValue(null); + } + + // Proxy manager depends on cache + registrations.proxyManager = asFunction(({ cache, config, logger }) => { + if (!cache) { + logger.warn('Cache is disabled, ProxyManager will have limited functionality'); + return null; + } + const manager = new ProxyManager( + cache, + config.proxy || {}, + logger + ); + return manager; + }).singleton(); + + // Conditionally register MongoDB client + if (config.mongodb?.enabled !== false) { + registrations.mongoClient = asFunction(({ mongoConfig, logger }) => { return new MongoDBClient(mongoConfig, logger); - }).singleton(), - - postgresClient: asFunction(({ postgresConfig, logger }) => { + }).singleton(); + } else { + registrations.mongoClient = asValue(null); + } + + // Conditionally register PostgreSQL client + if (config.postgres?.enabled !== false) { + registrations.postgresClient = asFunction(({ postgresConfig, logger }) => { return createPostgreSQLClient( { host: postgresConfig.host, @@ -106,9 +126,14 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { }, logger ); - }).singleton(), - - questdbClient: asFunction(({ questdbConfig, logger }) => { + }).singleton(); + } else { + registrations.postgresClient = asValue(null); + } + + // Conditionally register QuestDB client + if (config.questdb?.enabled !== false) { + registrations.questdbClient = asFunction(({ questdbConfig, logger }) => { console.log('Creating QuestDB client with config:', questdbConfig); return createQuestDBClient( { @@ -123,32 +148,35 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { }, logger ); - }).singleton(), - - // Queue manager - placeholder - queueManager: asFunction(() => { - // TODO: Create queue manager when decoupled - return null; - }).singleton(), - - // Browser automation - browser: asFunction(({ config, logger }) => { - return new Browser(logger, config.browser); - }).singleton(), - - // Build the IServiceContainer for handlers - serviceContainer: asFunction((cradle) => ({ - logger: cradle.logger, - cache: cradle.cache, - proxy: cradle.proxyManager, - browser: cradle.browser, - mongodb: cradle.mongoClient, - postgres: cradle.postgresClient, - questdb: cradle.questdbClient, - queue: cradle.queueManager, - } as IServiceContainer)).singleton(), - }); + }).singleton(); + } else { + registrations.questdbClient = asValue(null); + } + // Queue manager - placeholder + registrations.queueManager = asFunction(() => { + // TODO: Create queue manager when decoupled + return null; + }).singleton(); + + // Browser automation + registrations.browser = asFunction(({ config, logger }) => { + return new Browser(logger, config.browser); + }).singleton(); + + // Build the IServiceContainer for handlers + registrations.serviceContainer = asFunction((cradle) => ({ + logger: cradle.logger, + cache: cradle.cache, + proxy: cradle.proxyManager, + browser: cradle.browser, + mongodb: cradle.mongoClient, + postgres: cradle.postgresClient, + questdb: cradle.questdbClient, + queue: cradle.queueManager, + } as IServiceContainer)).singleton(); + + container.register(registrations); return container; } @@ -157,40 +185,53 @@ export function createServiceContainer(config: AppConfig): AwilixContainer { */ export async function initializeServices(container: AwilixContainer): Promise { const logger = container.resolve('logger'); + const config = container.resolve('config'); try { - // Wait for cache to be ready first + // Wait for cache to be ready first (if enabled) const cache = container.resolve('cache'); if (cache && typeof cache.waitForReady === 'function') { await cache.waitForReady(10000); logger.info('Cache is ready'); + } else if (config.redis?.enabled === false) { + logger.info('Cache is disabled'); } - // Initialize proxy manager + // Initialize proxy manager (depends on cache) const proxyManager = container.resolve('proxyManager'); if (proxyManager && typeof proxyManager.initialize === 'function') { await proxyManager.initialize(); logger.info('Proxy manager initialized'); + } else { + logger.info('Proxy manager is disabled (requires cache)'); } - // Connect database clients + // Connect MongoDB client (if enabled) const mongoClient = container.resolve('mongoClient'); if (mongoClient && typeof mongoClient.connect === 'function') { await mongoClient.connect(); logger.info('MongoDB connected'); + } else if (config.mongodb?.enabled === false) { + logger.info('MongoDB is disabled'); } + // Connect PostgreSQL client (if enabled) const postgresClient = container.resolve('postgresClient'); if (postgresClient && typeof postgresClient.connect === 'function') { await postgresClient.connect(); logger.info('PostgreSQL connected'); + } else if (config.postgres?.enabled === false) { + logger.info('PostgreSQL is disabled'); } - // const questdbClient = container.resolve('questdbClient'); - // if (questdbClient && typeof questdbClient.connect === 'function') { - // await questdbClient.connect(); - // logger.info('QuestDB connected'); - // } + // Connect QuestDB client (if enabled) + const questdbClient = container.resolve('questdbClient'); + if (questdbClient && typeof questdbClient.connect === 'function') { + await questdbClient.connect(); + logger.info('QuestDB connected'); + } else if (config.questdb?.enabled === false) { + logger.info('QuestDB is disabled'); + } // Initialize browser if configured const browser = container.resolve('browser'); diff --git a/libs/core/handlers/src/decorators/decorators.ts b/libs/core/handlers/src/decorators/decorators.ts index f37d358..9a1dbda 100644 --- a/libs/core/handlers/src/decorators/decorators.ts +++ b/libs/core/handlers/src/decorators/decorators.ts @@ -76,6 +76,22 @@ export function QueueSchedule( }; } +/** + * Disabled decorator - marks a handler as disabled for auto-registration + * Handlers marked with @Disabled() will be skipped during auto-registration + */ +export function Disabled() { + return function ( + target: T, + _context?: any + ) { + // Store disabled flag on the constructor + (target as any).__disabled = true; + + return target; + }; +} + /** * Combined decorator for scheduled operations * Automatically creates both an operation and a schedule diff --git a/libs/core/handlers/src/index.ts b/libs/core/handlers/src/index.ts index 54a2c6a..9659398 100644 --- a/libs/core/handlers/src/index.ts +++ b/libs/core/handlers/src/index.ts @@ -22,7 +22,7 @@ export type { IServiceContainer } from './types/service-container'; export { createJobHandler } from './types/types'; // Decorators -export { Handler, Operation, QueueSchedule, ScheduledOperation } from './decorators/decorators'; +export { Handler, Operation, QueueSchedule, ScheduledOperation, Disabled } from './decorators/decorators'; // Auto-registration utilities export { autoRegisterHandlers, createAutoHandlerRegistry } from './registry/auto-register'; diff --git a/libs/core/handlers/src/registry/auto-register.ts b/libs/core/handlers/src/registry/auto-register.ts index 1564c7d..274f647 100644 --- a/libs/core/handlers/src/registry/auto-register.ts +++ b/libs/core/handlers/src/registry/auto-register.ts @@ -108,6 +108,12 @@ export async function autoRegisterHandlers( for (const HandlerClass of handlerClasses) { const handlerName = HandlerClass.name; + // Check if handler is disabled + if ((HandlerClass as any).__disabled) { + logger.info(`Skipping disabled handler: ${handlerName} from ${relativePath}`); + continue; + } + if (dryRun) { logger.info(`[DRY RUN] Would register handler: ${handlerName} from ${relativePath}`); registered.push(handlerName); diff --git a/libs/core/handlers/src/types/service-container.ts b/libs/core/handlers/src/types/service-container.ts index 333e781..a7fc1aa 100644 --- a/libs/core/handlers/src/types/service-container.ts +++ b/libs/core/handlers/src/types/service-container.ts @@ -12,15 +12,15 @@ import type { ProxyManager } from '@stock-bot/proxy'; export interface IServiceContainer { // Core infrastructure readonly logger: any; // Logger instance - readonly cache: any; // Cache provider (Redis/Dragonfly) - readonly queue: any; // Queue manager (BullMQ) - readonly proxy: ProxyManager; // Proxy manager service + readonly cache?: any; // Cache provider (Redis/Dragonfly) - 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 - readonly mongodb: any; // MongoDB client - readonly postgres: any; // PostgreSQL client - readonly questdb: any; // QuestDB client (time-series) + // 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; diff --git a/libs/data/cache/src/redis-cache.ts b/libs/data/cache/src/redis-cache.ts index 050c48d..3f932e2 100644 --- a/libs/data/cache/src/redis-cache.ts +++ b/libs/data/cache/src/redis-cache.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; import { RedisConnectionManager } from './connection-manager'; -import { CacheOptions, CacheProvider, CacheStats } from './types'; +import type { CacheOptions, CacheProvider, CacheStats } from './types'; /** * Simplified Redis-based cache provider using connection manager diff --git a/libs/data/mongodb/src/client.ts b/libs/data/mongodb/src/client.ts index d47ef48..bf99898 100644 --- a/libs/data/mongodb/src/client.ts +++ b/libs/data/mongodb/src/client.ts @@ -1,5 +1,6 @@ import type { Logger } from '@stock-bot/core/logger'; -import { Collection, Db, MongoClient, OptionalUnlessRequiredId } from 'mongodb'; +import { Collection, Db, MongoClient } from 'mongodb'; +import type { OptionalUnlessRequiredId } from 'mongodb'; import type { ConnectionEvents, DocumentBase, DynamicPoolConfig, MongoDBClientConfig, PoolMetrics } from './types'; /** diff --git a/libs/data/postgres/src/client.ts b/libs/data/postgres/src/client.ts index 1320951..1e43350 100644 --- a/libs/data/postgres/src/client.ts +++ b/libs/data/postgres/src/client.ts @@ -1,4 +1,5 @@ -import { Pool, QueryResultRow } from 'pg'; +import { Pool } from 'pg'; +import type { QueryResultRow } from 'pg'; import { PostgreSQLHealthMonitor } from './health'; import { PostgreSQLQueryBuilder } from './query-builder'; import { PostgreSQLTransactionManager } from './transactions'; diff --git a/libs/services/browser/src/browser.ts b/libs/services/browser/src/browser.ts index 926bd0f..f413ec7 100644 --- a/libs/services/browser/src/browser.ts +++ b/libs/services/browser/src/browser.ts @@ -1,4 +1,5 @@ -import { BrowserContext, chromium, Page, Browser as PlaywrightBrowser } from 'playwright'; +import { chromium } from 'playwright'; +import type { BrowserContext, Page, Browser as PlaywrightBrowser } from 'playwright'; import type { BrowserOptions, NetworkEvent, NetworkEventHandler } from './types'; export class Browser { diff --git a/libs/services/queue/src/queue-manager.ts b/libs/services/queue/src/queue-manager.ts index abdbce1..de5c45c 100644 --- a/libs/services/queue/src/queue-manager.ts +++ b/libs/services/queue/src/queue-manager.ts @@ -1,4 +1,5 @@ -import { CacheProvider, createCache } from '@stock-bot/cache'; +import { createCache } from '@stock-bot/cache'; +import type { CacheProvider } from '@stock-bot/cache'; import { getLogger } from '@stock-bot/logger'; import { Queue, type QueueWorkerConfig } from './queue'; import { QueueRateLimiter } from './rate-limiter';