/** * Dependency Management System für Module * Verwaltet Dependencies und Initialisierungsreihenfolge */ import { Logger } from './logger.js'; /** * @typedef {Object} ModuleDependency * @property {string} name - Module name * @property {string} version - Required version (semver) * @property {boolean} optional - Whether dependency is optional */ /** * @typedef {Object} ModuleDefinition * @property {string} name - Module name * @property {string} version - Module version * @property {ModuleDependency[]} dependencies - Required dependencies * @property {string[]} provides - Services this module provides * @property {number} priority - Initialization priority (higher = earlier) */ export class DependencyManager { constructor() { /** @type {Map} */ this.modules = new Map(); /** @type {Map} */ this.dependents = new Map(); // who depends on this module /** @type {Set} */ this.initialized = new Set(); /** @type {Set} */ this.initializing = new Set(); /** @type {string[]} */ this.initializationOrder = []; } /** * Register a module with its dependencies * @param {ModuleDefinition} definition - Module definition */ register(definition) { if (this.modules.has(definition.name)) { Logger.warn(`[DependencyManager] Module '${definition.name}' already registered`); return; } // Validate definition if (!this.validateDefinition(definition)) { return; } this.modules.set(definition.name, definition); // Build dependents map definition.dependencies.forEach(dep => { if (!this.dependents.has(dep.name)) { this.dependents.set(dep.name, []); } this.dependents.get(dep.name).push(definition.name); }); Logger.info(`[DependencyManager] Registered '${definition.name}' v${definition.version}`); } /** * Calculate initialization order based on dependencies * @returns {string[]} Ordered list of module names */ calculateInitializationOrder() { const visited = new Set(); const visiting = new Set(); const order = []; /** * Depth-first search with cycle detection * @param {string} moduleName */ const visit = (moduleName) => { if (visiting.has(moduleName)) { throw new Error(`Circular dependency detected involving '${moduleName}'`); } if (visited.has(moduleName)) { return; } const module = this.modules.get(moduleName); if (!module) { return; // Module not registered, might be optional } visiting.add(moduleName); // Visit dependencies first module.dependencies.forEach(dep => { if (!dep.optional || this.modules.has(dep.name)) { visit(dep.name); } }); visiting.delete(moduleName); visited.add(moduleName); order.push(moduleName); }; // Sort by priority first, then resolve dependencies const modulesByPriority = Array.from(this.modules.entries()) .sort(([, a], [, b]) => (b.priority || 0) - (a.priority || 0)) .map(([name]) => name); modulesByPriority.forEach(moduleName => { if (!visited.has(moduleName)) { visit(moduleName); } }); this.initializationOrder = order; Logger.info(`[DependencyManager] Initialization order: ${order.join(' → ')}`); return order; } /** * Check if all dependencies for a module are satisfied * @param {string} moduleName - Module to check * @returns {Object} Dependency check result */ checkDependencies(moduleName) { const module = this.modules.get(moduleName); if (!module) { return { satisfied: false, missing: [], reason: `Module '${moduleName}' not registered` }; } const missing = []; const optional = []; module.dependencies.forEach(dep => { const depModule = this.modules.get(dep.name); if (!depModule) { if (dep.optional) { optional.push(dep.name); } else { missing.push(dep.name); } } else if (!this.initialized.has(dep.name)) { if (!dep.optional) { missing.push(`${dep.name} (not initialized)`); } } }); return { satisfied: missing.length === 0, missing, optional, reason: missing.length > 0 ? `Missing: ${missing.join(', ')}` : 'OK' }; } /** * Mark module as initialized * @param {string} moduleName - Module name */ markInitialized(moduleName) { this.initialized.add(moduleName); this.initializing.delete(moduleName); Logger.info(`[DependencyManager] '${moduleName}' initialized`); } /** * Mark module as initializing * @param {string} moduleName - Module name */ markInitializing(moduleName) { this.initializing.add(moduleName); } /** * Get modules that depend on the given module * @param {string} moduleName - Module name * @returns {string[]} Dependent module names */ getDependents(moduleName) { return this.dependents.get(moduleName) || []; } /** * Get module definition * @param {string} moduleName - Module name * @returns {ModuleDefinition|undefined} Module definition */ getModule(moduleName) { return this.modules.get(moduleName); } /** * Check if module is ready to initialize * @param {string} moduleName - Module name * @returns {boolean} Ready status */ isReadyToInitialize(moduleName) { if (this.initialized.has(moduleName) || this.initializing.has(moduleName)) { return false; } const check = this.checkDependencies(moduleName); return check.satisfied; } /** * Get initialization status * @returns {Object} Status report */ getStatus() { const total = this.modules.size; const initialized = this.initialized.size; const initializing = this.initializing.size; const pending = total - initialized - initializing; return { total, initialized, initializing, pending, modules: { initialized: Array.from(this.initialized), initializing: Array.from(this.initializing), pending: Array.from(this.modules.keys()).filter(name => !this.initialized.has(name) && !this.initializing.has(name) ) } }; } /** * Validate module definition * @private * @param {ModuleDefinition} definition - Module definition to validate * @returns {boolean} Validation result */ validateDefinition(definition) { if (!definition.name || typeof definition.name !== 'string') { Logger.error('[DependencyManager] Module name is required and must be string'); return false; } if (!definition.version || typeof definition.version !== 'string') { Logger.error(`[DependencyManager] Version is required for module '${definition.name}'`); return false; } if (!Array.isArray(definition.dependencies)) { Logger.error(`[DependencyManager] Dependencies must be array for module '${definition.name}'`); return false; } // Validate dependencies for (const dep of definition.dependencies) { if (!dep.name || typeof dep.name !== 'string') { Logger.error(`[DependencyManager] Invalid dependency in module '${definition.name}': missing name`); return false; } } return true; } /** * Reset dependency manager */ reset() { this.modules.clear(); this.dependents.clear(); this.initialized.clear(); this.initializing.clear(); this.initializationOrder = []; Logger.info('[DependencyManager] Reset complete'); } /** * Create module definition helper * @param {string} name - Module name * @param {string} version - Module version * @returns {Object} Module definition builder */ static createDefinition(name, version) { const definition = { name, version, dependencies: [], provides: [], priority: 0 }; // Create a chainable API const builder = { depends(moduleName, moduleVersion = '*', optional = false) { definition.dependencies.push({ name: moduleName, version: moduleVersion, optional }); return this; }, provides(...services) { definition.provides.push(...services); return this; }, priority(level) { definition.priority = level; return this; }, // Return the final definition build() { return definition; } }; // Make the builder return the definition when accessed as an object Object.setPrototypeOf(builder, definition); // Allow direct access to the definition properties Object.keys(definition).forEach(key => { if (!(key in builder)) { Object.defineProperty(builder, key, { get() { return definition[key]; }, set(value) { definition[key] = value; }, enumerable: true, configurable: true }); } }); return builder; } } // Global instance export const dependencyManager = new DependencyManager(); // Debug access if (typeof window !== 'undefined') { window.dependencyManager = dependencyManager; window.depStatus = () => dependencyManager.getStatus(); }