- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
353 lines
10 KiB
JavaScript
353 lines
10 KiB
JavaScript
/**
|
|
* 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<string, ModuleDefinition>} */
|
|
this.modules = new Map();
|
|
|
|
/** @type {Map<string, string[]>} */
|
|
this.dependents = new Map(); // who depends on this module
|
|
|
|
/** @type {Set<string>} */
|
|
this.initialized = new Set();
|
|
|
|
/** @type {Set<string>} */
|
|
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();
|
|
} |