stock-bot/apps/data-ingestion/src/index.ts

231 lines
No EOL
6.8 KiB
TypeScript

/**
* 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<typeof Bun.serve> | 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');