Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
353
resources/js/core/DependencyManager.js
Normal file
353
resources/js/core/DependencyManager.js
Normal file
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
Reference in New Issue
Block a user