/** * Service Initialization Manager * * Manages the proper initialization order of services to prevent race conditions * and ensure dependencies are available when services are created. * * @author Matthew Raymer * @since 2025-08-25 */ import { logger } from "../utils/logger"; /** * Service initialization status tracking */ interface ServiceStatus { name: string; initialized: boolean; dependencies: string[]; error?: string; } /** * Service initialization manager to prevent race conditions */ export class ServiceInitializationManager { private static instance: ServiceInitializationManager; private serviceStatuses = new Map(); private initializationPromise: Promise | null = null; private constructor() {} /** * Get singleton instance */ static getInstance(): ServiceInitializationManager { if (!ServiceInitializationManager.instance) { ServiceInitializationManager.instance = new ServiceInitializationManager(); } return ServiceInitializationManager.instance; } /** * Register a service that needs initialization */ registerService(name: string, dependencies: string[] = []): void { this.serviceStatuses.set(name, { name, initialized: false, dependencies, }); logger.debug("[ServiceInit] 🔧 Service registered:", { name, dependencies, totalServices: this.serviceStatuses.size, }); } /** * Mark a service as initialized */ markInitialized(name: string): void { const status = this.serviceStatuses.get(name); if (status) { status.initialized = true; logger.debug("[ServiceInit] ✅ Service initialized:", { name, totalInitialized: this.getInitializedCount(), totalServices: this.serviceStatuses.size, }); } } /** * Mark a service as failed */ markFailed(name: string, error: string): void { const status = this.serviceStatuses.get(name); if (status) { status.error = error; logger.error("[ServiceInit] ❌ Service failed:", { name, error, totalFailed: this.getFailedCount(), }); } } /** * Get count of initialized services */ private getInitializedCount(): number { return Array.from(this.serviceStatuses.values()).filter( (s) => s.initialized, ).length; } /** * Get count of failed services */ private getFailedCount(): number { return Array.from(this.serviceStatuses.values()).filter((s) => s.error) .length; } /** * Wait for all services to be initialized */ async waitForInitialization(): Promise { if (this.initializationPromise) { return this.initializationPromise; } this.initializationPromise = new Promise((resolve, reject) => { const checkInterval = setInterval(() => { const totalServices = this.serviceStatuses.size; const initializedCount = this.getInitializedCount(); const failedCount = this.getFailedCount(); logger.debug("[ServiceInit] 🔍 Initialization progress:", { totalServices, initializedCount, failedCount, remaining: totalServices - initializedCount - failedCount, }); if (failedCount > 0) { clearInterval(checkInterval); const failedServices = Array.from(this.serviceStatuses.values()) .filter((s) => s.error) .map((s) => `${s.name}: ${s.error}`); const error = new Error( `Service initialization failed: ${failedServices.join(", ")}`, ); logger.error("[ServiceInit] ❌ Initialization failed:", error); reject(error); } else if (initializedCount === totalServices) { clearInterval(checkInterval); logger.info( "[ServiceInit] 🎉 All services initialized successfully:", { totalServices, initializedCount, }, ); resolve(); } }, 100); // Timeout after 30 seconds setTimeout(() => { clearInterval(checkInterval); const error = new Error( "Service initialization timeout after 30 seconds", ); logger.error("[ServiceInit] ⏰ Initialization timeout:", error); reject(error); }, 30000); }); return this.initializationPromise; } /** * Get initialization status summary */ getStatusSummary(): { total: number; initialized: number; failed: number; pending: number; services: ServiceStatus[]; } { const services = Array.from(this.serviceStatuses.values()); const total = services.length; const initialized = services.filter((s) => s.initialized).length; const failed = services.filter((s) => s.error).length; const pending = total - initialized - failed; return { total, initialized, failed, pending, services, }; } /** * Reset the manager (useful for testing) */ reset(): void { this.serviceStatuses.clear(); this.initializationPromise = null; logger.debug("[ServiceInit] 🔄 Manager reset"); } } /** * Convenience function to get the service initialization manager */ export const getServiceInitManager = (): ServiceInitializationManager => { return ServiceInitializationManager.getInstance(); };