231 lines
No EOL
6.8 KiB
TypeScript
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'); |