import { IService } from '../interfaces'; /** * Service factory function */ export type ServiceFactory = () => T | Promise; /** * Service registration options */ export interface ServiceRegistration { factory: ServiceFactory; singleton?: boolean; dependencies?: string[]; } /** * Dependency injection container */ export class DIContainer { private services = new Map(); private singletons = new Map(); private resolving = new Set(); // Track pending resolutions to allow concurrent callers to await the same promise private pendingResolutions = new Map>(); /** * Register a service with the container */ register(token: string, factory: ServiceFactory, options?: Omit, 'factory'>): void { this.services.set(token, { factory, singleton: options?.singleton ?? false, dependencies: options?.dependencies ?? [] }); } /** * Register a singleton service */ singleton(token: string, factory: ServiceFactory, dependencies?: string[]): void { this.register(token, factory, { singleton: true, dependencies }); } /** * Register a transient service */ transient(token: string, factory: ServiceFactory, dependencies?: string[]): void { this.register(token, factory, { singleton: false, dependencies }); } /** * Register an existing instance as a singleton */ registerInstance(token: string, instance: T): void { this.singletons.set(token, instance); // Also register a factory that returns the instance this.register(token, () => instance, { singleton: true }); } /** * Resolve a service from the container */ async resolve(token: string): Promise { // Return singleton if already created (prefer fast path) if (this.singletons.has(token)) { return this.singletons.get(token); } // If a resolution is already in progress for this token, share the promise if (this.pendingResolutions.has(token)) { return await this.pendingResolutions.get(token) as T; } // Check for circular dependencies (true recursion within a single resolution chain) if (this.resolving.has(token)) { throw new Error(`Circular dependency detected: ${token}`); } const registration = this.services.get(token); if (!registration) { throw new Error(`Service not found: ${token}`); } this.resolving.add(token); // Create a shared promise for this resolution so concurrent callers can await it const resolutionPromise = (async () => { try { // Resolve dependencies first const dependencies: any[] = []; if (registration.dependencies) { for (const dep of registration.dependencies) { dependencies.push(await this.resolve(dep)); } } // Create service instance const instance = await registration.factory(); // Initialize service if it implements IService if (this.isService(instance)) { await instance.initialize?.(); } // Store singleton if (registration.singleton) { this.singletons.set(token, instance); } return instance as T; } finally { this.resolving.delete(token); this.pendingResolutions.delete(token); } })(); this.pendingResolutions.set(token, resolutionPromise); return await resolutionPromise; } /** * Resolve service synchronously (for already initialized singletons) */ resolveSync(token: string): T { if (this.singletons.has(token)) { return this.singletons.get(token); } throw new Error(`Service not available synchronously: ${token}`); } /** * Check if service is registered */ has(token: string): boolean { return this.services.has(token); } /** * Get all registered service tokens */ getRegisteredServices(): string[] { return Array.from(this.services.keys()); } /** * Clear all services and singletons */ async clear(): Promise { // Destroy all singleton services const entries = Array.from(this.singletons.entries()); for (const [token, instance] of entries) { if (this.isService(instance)) { await instance.destroy?.(); } } this.services.clear(); this.singletons.clear(); this.resolving.clear(); } /** * Remove a specific service */ async remove(token: string): Promise { // Destroy singleton if exists if (this.singletons.has(token)) { const instance = this.singletons.get(token); if (this.isService(instance)) { await instance.destroy?.(); } this.singletons.delete(token); } this.services.delete(token); } /** * Health check for all services */ async healthCheck(): Promise> { const health: Record = {}; const entries = Array.from(this.singletons.entries()); for (const [token, instance] of entries) { if (this.isService(instance)) { try { health[token] = await instance.isHealthy?.() ?? true; } catch (error) { health[token] = false; } } else { health[token] = true; } } return health; } /** * Check if object implements IService interface */ private isService(obj: any): obj is IService { return obj && typeof obj === 'object' && 'id' in obj && 'name' in obj; } } // Global container instance export const container = new DIContainer();