import { EventEmitter } from 'node:events' import { metadataRegistry } from './metadata-registry' import type { ClassConstructor, ParamInfo, Scope, ServiceMetadata } from './types' export class Container extends EventEmitter { private services = new Map() private aliases = new Map() private instances = new Map() private resolutionStack = new Set() // Stores classified by decorator type private decoratorStores = new Map>() use( constructor: ClassConstructor, scope: Scope = 'singleton', autorun: boolean = false, params: ParamInfo[] = [], decoratorType: string = 'service', // Decorator type ) { return this.register(constructor, scope, autorun, params, decoratorType) } /** * Register a class in the container along with its dependencies. */ register( constructor: ClassConstructor, scope: Scope = 'singleton', autorun: boolean = false, params: ParamInfo[] = [], decoratorType: string = 'service', // Decorator type ): void { if (this.services.has(constructor)) return // Get parameter info from metadata registry if not provided const paramInfo = params.length > 0 ? params : metadataRegistry.getParamInfo(constructor) const metadata: ServiceMetadata = { constructor, scope, autorun, params: paramInfo, decoratorType // Save decorator type } this.services.set(constructor, metadata) // Also register in metadata registry metadataRegistry.registerService(constructor, scope, autorun) // Add to decorator store this.addToDecoratorStore(decoratorType, constructor) this.emit('registered', { name: constructor.name, constructor, decoratorType }) } // Add service to specific store by decorator type private addToDecoratorStore(decoratorType: string, constructor: ClassConstructor): void { if (!this.decoratorStores.has(decoratorType)) { this.decoratorStores.set(decoratorType, new Set()) } this.decoratorStores.get(decoratorType)!.add(constructor) } // Get services by decorator type getServicesByDecorator(decoratorType: string): ClassConstructor[] { return Array.from(this.decoratorStores.get(decoratorType) || []) } // Get all decorator stores getDecoratorStores(): Map { const result = new Map() for (const [decoratorType, constructors] of this.decoratorStores) { result.set(decoratorType, Array.from(constructors)) } return result } registerAlias(abstractKey: any, concreteClass: ClassConstructor): void { this.aliases.set(abstractKey, concreteClass) } private resolveToken(token: any): ClassConstructor | null { if (this.services.has(token)) return token return this.aliases.get(token) ?? null } resolve(constructor: ClassConstructor): T { if (this.resolutionStack.has(constructor)) { throw new Error( `Cyclic dependency detected for type: ${constructor.name}`, ) } const metadata = this.services.get(constructor) if (!metadata) { throw new Error(`No service registered for type: ${constructor.name}`) } if (metadata.scope === 'singleton' && this.instances.has(constructor)) { return this.instances.get(constructor) } this.resolutionStack.add(constructor) try { const args: any[] = [] for (let i = 0; i < metadata.params.length; i++) { const param = metadata.params[i] if (!param?.type) { args[i] = undefined continue } const resolvedConstructor = this.resolveToken(param.type) if (!resolvedConstructor) { if (param.optional) { const fallback = typeof param.fallback === 'function' ? param.fallback() : param.fallback args[i] = fallback continue } throw new Error( `Missing required dependency '${param.type?.name}' for '${constructor.name}' at index ${param.index}`, ) } args[i] = this.resolve(resolvedConstructor) } const instance = new metadata.constructor(...args) if (metadata.scope === 'singleton') { this.instances.set(constructor, instance) } this.emit('resolved', { name: constructor.name, instance }) return instance } finally { this.resolutionStack.delete(constructor) } } get(constructor: ClassConstructor): T { return this.resolve(constructor) } getAllServices(): any[] { return Array.from(this.instances.values()) } // Get instances by decorator type getInstancesByDecorator(decoratorType: string): any[] { const constructors = this.getServicesByDecorator(decoratorType) return constructors.map(constructor => { try { return this.get(constructor) } catch { return null } }).filter(Boolean) } clear(): void { this.services.clear() this.aliases.clear() this.instances.clear() this.resolutionStack.clear() this.decoratorStores.clear() // Clear decorator stores metadataRegistry.clear() // Clear metadata registry } } // Default shared instance export const container = new Container()