/** * Data Ingestion Service with Improved Dependency Injection * This is the new version using type-safe services and constructor injection */ // Framework imports import { initializeServiceConfig } from '@stock-bot/config'; import { Hono } from 'hono'; import { cors } from 'hono/cors'; // Library imports import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger'; import { Shutdown } from '@stock-bot/shutdown'; import { ProxyManager } from '@stock-bot/utils'; import { createDataIngestionServices, disposeDataIngestionServices, type IDataIngestionServices } from '@stock-bot/di'; import { handlerRegistry } from '@stock-bot/types'; // Local imports import { createRoutes } from './routes/create-routes'; import { initializeQMProviderNew } from './handlers/qm/qm.handler'; const config = initializeServiceConfig(); console.log('Data Service Configuration:', JSON.stringify(config, null, 2)); const serviceConfig = config.service; if (config.log) { setLoggerConfig({ logLevel: config.log.level, logConsole: true, logFile: false, environment: config.environment, hideObject: config.log.hideObject, }); } // Create logger AFTER config is set const logger = getLogger('data-ingestion'); const PORT = serviceConfig.port; let server: ReturnType | null = null; let services: IDataIngestionServices | null = null; let app: Hono | null = null; // Initialize shutdown manager const shutdown = Shutdown.getInstance({ timeout: 15000 }); // Initialize services with new DI pattern async function initializeServices() { logger.info('Initializing data-ingestion service with improved DI...'); try { // Create all services using the service factory logger.debug('Creating services using service factory...'); services = await createDataIngestionServices(config); logger.info('All services created successfully'); // Create app with routes that have access to services app = new Hono(); // Add CORS middleware app.use( '*', cors({ origin: '*', allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], allowHeaders: ['Content-Type', 'Authorization'], credentials: false, }) ); // Create and mount routes with services const routes = createRoutes(services); app.route('/', routes); // Initialize proxy manager logger.debug('Initializing proxy manager...'); await ProxyManager.initialize(); logger.info('Proxy manager initialized'); // Initialize handlers with new DI pattern logger.debug('Initializing data handlers with new DI pattern...'); // Initialize QM handler with new pattern initializeQMProviderNew(services); // TODO: Convert other handlers to new pattern // initializeWebShareProviderNew(services); // initializeIBProviderNew(services); // initializeProxyProviderNew(services); logger.info('Data handlers initialized with new DI pattern'); // Create scheduled jobs from registered handlers logger.debug('Creating scheduled jobs from registered handlers...'); const allHandlers = handlerRegistry.getAllHandlers(); let totalScheduledJobs = 0; for (const [handlerName, config] of allHandlers) { if (config.scheduledJobs && config.scheduledJobs.length > 0) { const queue = services.queue.getQueue(handlerName); for (const scheduledJob of config.scheduledJobs) { // Include handler and operation info in job data const jobData = { handler: handlerName, operation: scheduledJob.operation, payload: scheduledJob.payload || {}, }; // Build job options from scheduled job config const jobOptions = { priority: scheduledJob.priority, delay: scheduledJob.delay, repeat: { immediately: scheduledJob.immediately, }, }; await queue.addScheduledJob( scheduledJob.operation, jobData, scheduledJob.cronPattern, jobOptions ); totalScheduledJobs++; logger.debug('Scheduled job created', { handler: handlerName, operation: scheduledJob.operation, cronPattern: scheduledJob.cronPattern, immediately: scheduledJob.immediately, priority: scheduledJob.priority, }); } } } logger.info('Scheduled jobs created', { totalJobs: totalScheduledJobs }); // Start queue workers logger.debug('Starting queue workers...'); services.queue.startAllWorkers(); logger.info('Queue workers started'); logger.info('All services initialized successfully'); } catch (error) { logger.error('Failed to initialize services', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); throw error; } } // Start server async function startServer() { await initializeServices(); if (!app) { throw new Error('App not initialized'); } server = Bun.serve({ port: PORT, fetch: app.fetch, development: config.environment === 'development', }); logger.info(`Data-ingestion service started on port ${PORT}`); } // Register shutdown handlers with priorities // Priority 1: Queue system (highest priority) shutdown.onShutdownHigh(async () => { logger.info('Shutting down queue system...'); try { if (services?.queue) { await services.queue.shutdown(); } logger.info('Queue system shut down'); } catch (error) { logger.error('Error shutting down queue system', { error }); } }, 'Queue System'); // Priority 1: HTTP Server (high priority) shutdown.onShutdownHigh(async () => { if (server) { logger.info('Stopping HTTP server...'); try { server.stop(); logger.info('HTTP server stopped'); } catch (error) { logger.error('Error stopping HTTP server', { error }); } } }, 'HTTP Server'); // Priority 2: Services and connections (medium priority) shutdown.onShutdownMedium(async () => { logger.info('Disposing services and connections...'); try { if (services) { await disposeDataIngestionServices(services); logger.info('All services disposed successfully'); } } catch (error) { logger.error('Error disposing services', { error }); } }, 'Services'); // Priority 3: Logger shutdown (lowest priority - runs last) shutdown.onShutdownLow(async () => { try { logger.info('Shutting down loggers...'); await shutdownLoggers(); // Don't log after shutdown } catch { // Silently ignore logger shutdown errors } }, 'Loggers'); // Start the service startServer().catch(error => { logger.fatal('Failed to start data service', { error }); process.exit(1); }); logger.info('Data service startup initiated with improved DI pattern');