UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

401 lines 14.6 kB
/** * Dependency Injection Container - Living Spiral Architecture Foundation * Resolves circular dependencies through interface abstraction and service registration * * Council Perspectives Applied: * - Architect: Clean separation of concerns with dependency inversion * - Maintainer: Easy service registration and lifecycle management * - Security Guardian: Controlled service access and initialization * - Performance Engineer: Lazy loading and singleton optimization * - Explorer: Extensible plugin architecture for new services */ import { EventEmitter } from 'events'; import { logger } from '../logger.js'; // Scoped dependency management export class DependencyScope { scopedInstances = new Map(); parent; constructor(parent) { this.parent = parent; } resolve(token) { const tokenName = typeof token === 'string' ? token : token.name; // Check if already resolved in this scope if (this.scopedInstances.has(tokenName)) { return this.scopedInstances.get(tokenName); } // Resolve from parent and cache in scope const instance = this.parent.resolve(token, { scope: this }); this.scopedInstances.set(tokenName, instance); return instance; } dispose() { // Dispose all scoped instances that have dispose methods for (const [tokenName, instance] of this.scopedInstances) { if (instance && typeof instance.dispose === 'function') { try { instance.dispose(); } catch (error) { logger.warn(`Error disposing service ${tokenName}:`, error); } } } this.scopedInstances.clear(); } } /** * Dependency Injection Container Implementation * Provides service registration, resolution, and lifecycle management */ export class DependencyContainer extends EventEmitter { services = new Map(); resolutionStack = []; initializationOrder = []; isDisposed = false; constructor() { super(); this.setMaxListeners(100); // Allow many service registrations } /** * Register a service with the container */ register(token, factory, options = {}) { if (this.isDisposed) { throw new Error('Cannot register services on disposed container'); } const tokenName = typeof token === 'string' ? token : token.name; if (this.services.has(tokenName)) { logger.warn(`Service ${tokenName} is already registered. Replacing registration.`); } const registration = { token: tokenName, factory, options: { lifecycle: 'singleton', lazy: true, dependencies: [], ...options, }, initialized: false, dependencies: options.dependencies || [], }; this.services.set(tokenName, registration); this.emit('serviceRegistered', { token: tokenName, options }); logger.debug(`Registered service: ${tokenName} (${registration.options.lifecycle})`); } /** * Register a class as a service with automatic dependency injection */ registerClass(token, constructor, dependencies = [], options = {}) { const factory = container => { const resolvedDependencies = dependencies.map(dep => container.resolve(dep)); return new constructor(...resolvedDependencies); }; this.register(token, factory, { ...options, dependencies, }); } /** * Register a value as a singleton service */ registerValue(token, value) { const factory = () => value; this.register(token, factory, { lifecycle: 'singleton', lazy: false }); } /** * Resolve a service by token (synchronous) */ resolve(token, context = {}) { if (this.isDisposed) { throw new Error('Cannot resolve services from disposed container'); } const tokenName = typeof token === 'string' ? token : token.name; const registration = this.services.get(tokenName); if (!registration) { throw new Error(`Service not registered: ${tokenName}`); } // Check for circular dependencies if (this.resolutionStack.includes(tokenName)) { const cycle = [...this.resolutionStack, tokenName].join(' → '); throw new Error(`Circular dependency detected: ${cycle}`); } // Handle different lifecycles - return Promise for async factories switch (registration.options.lifecycle) { case 'singleton': return this.resolveSingletonSync(registration, context); case 'scoped': if (context.scope) { return context.scope.resolve(tokenName); } // Fall through to transient if no scope provided return this.resolveTransientSync(registration, context); case 'transient': return this.resolveTransientSync(registration, context); default: throw new Error(`Unknown lifecycle: ${registration.options.lifecycle}`); } } /** * Resolve a service asynchronously * ENHANCED: Properly handles async factories by using async resolution path */ async resolveAsync(token, context = {}) { if (this.isDisposed) { throw new Error('Cannot resolve services from disposed container'); } const tokenName = typeof token === 'string' ? token : token.name; const registration = this.services.get(tokenName); if (!registration) { throw new Error(`Service not registered: ${tokenName}`); } // Check for circular dependencies if (this.resolutionStack.includes(tokenName)) { const cycle = [...this.resolutionStack, tokenName].join(' → '); throw new Error(`Circular dependency detected: ${cycle}`); } // Handle different lifecycles using ASYNC resolution path switch (registration.options.lifecycle) { case 'singleton': return await this.resolveSingleton(registration, context); case 'scoped': if (context.scope) { const result = context.scope.resolve(tokenName); return result && typeof result.then === 'function' ? await result : result; } // Fall through to transient if no scope provided return await this.resolveTransient(registration, context); case 'transient': return await this.resolveTransient(registration, context); default: throw new Error(`Unknown lifecycle: ${registration.options.lifecycle}`); } } /** * Create a new dependency scope */ createScope() { return new DependencyScope(this); } /** * Check if a service is registered */ isRegistered(token) { const tokenName = typeof token === 'string' ? token : token.name; return this.services.has(tokenName); } /** * Get all registered service names */ getRegisteredServices() { return Array.from(this.services.keys()); } /** * Get service registration metadata */ getServiceInfo(token) { const tokenName = typeof token === 'string' ? token : token.name; const registration = this.services.get(tokenName); return registration ? registration.options : null; } /** * Initialize all eager services */ async initializeEagerServices() { const eagerServices = Array.from(this.services.entries()) .filter(([_, reg]) => !reg.options.lazy) .map(([token, _]) => token); for (const token of eagerServices) { try { await this.resolveAsync(token); this.initializationOrder.push(token); } catch (error) { logger.error(`Failed to initialize eager service ${token}:`, error); throw error; } } this.emit('eagerServicesInitialized', { services: eagerServices }); } /** * Dispose the container and all singleton instances */ async dispose() { if (this.isDisposed) { return; } this.isDisposed = true; this.emit('disposing'); // Dispose singleton instances in reverse initialization order const disposalOrder = [...this.initializationOrder].reverse(); for (const token of disposalOrder) { const registration = this.services.get(token); if (registration?.instance && typeof registration.instance.dispose === 'function') { try { await registration.instance.dispose(); } catch (error) { logger.warn(`Error disposing service ${token}:`, error); } } } this.services.clear(); this.resolutionStack = []; this.initializationOrder = []; this.removeAllListeners(); this.emit('disposed'); } /** * Resolve singleton service (synchronous - may return Promise for async factories) */ resolveSingletonSync(registration, context) { if (registration.instance) { return registration.instance; } registration.instance = this.createInstanceSync(registration, context); if (!this.initializationOrder.includes(registration.token)) { this.initializationOrder.push(registration.token); } return registration.instance; } /** * Resolve singleton service (async) */ async resolveSingleton(registration, context) { if (registration.instance) { // If instance is a Promise, await it if (registration.instance && typeof registration.instance.then === 'function') { return await registration.instance; } return registration.instance; } registration.instance = await this.createInstance(registration, context); if (!this.initializationOrder.includes(registration.token)) { this.initializationOrder.push(registration.token); } return registration.instance; } /** * Resolve transient service (synchronous) */ resolveTransientSync(registration, context) { return this.createInstanceSync(registration, context); } /** * Resolve transient service (async) */ async resolveTransient(registration, context) { return await this.createInstance(registration, context); } /** * Create service instance using factory (synchronous - may return Promise) */ createInstanceSync(registration, context) { this.resolutionStack.push(registration.token); try { const instance = registration.factory(this); registration.initialized = true; this.emit('serviceResolved', { token: registration.token, lifecycle: registration.options.lifecycle, }); return instance; } catch (error) { logger.error(`Error creating instance for service ${registration.token}:`, error); throw error; } finally { this.resolutionStack.pop(); } } /** * Create service instance using factory (async) * FIXED: Now properly handles async factory functions that return Promises */ async createInstance(registration, context) { this.resolutionStack.push(registration.token); try { const instance = registration.factory(this); // FIX: Properly await async factories that return Promises const resolvedInstance = instance && typeof instance.then === 'function' ? await instance : instance; registration.initialized = true; this.emit('serviceResolved', { token: registration.token, lifecycle: registration.options.lifecycle, }); return resolvedInstance; } catch (error) { logger.error(`Error creating instance for service ${registration.token}:`, error); throw error; } finally { this.resolutionStack.pop(); } } /** * Validate service dependency graph for circular dependencies */ validateDependencyGraph() { const cycles = []; const visited = new Set(); const recursionStack = new Set(); const hasCycle = (token, path = []) => { if (recursionStack.has(token)) { cycles.push([...path, token].join(' → ')); return true; } if (visited.has(token)) { return false; } visited.add(token); recursionStack.add(token); const registration = this.services.get(token); if (registration) { for (const dependency of registration.dependencies) { if (hasCycle(dependency, [...path, token])) { return true; } } } recursionStack.delete(token); return false; }; for (const token of this.services.keys()) { if (!visited.has(token)) { hasCycle(token); } } return { isValid: cycles.length === 0, cycles: cycles, }; } } /** * Global dependency container instance */ export const globalContainer = new DependencyContainer(); /** * Service token factory for type-safe registration */ export function createServiceToken(name) { return { name }; } /** * Decorator for automatic service registration */ export function Injectable(token, options = {}) { return function (constructor) { // This would be used with a metadata reflection system // For now, it serves as documentation return constructor; }; } //# sourceMappingURL=dependency-container.js.map