/** * Module Registry * Manages module registration and dependency tracking */ export class ModuleRegistry { constructor() { this.modules = {}; this.readyPromises = {}; this.moduleDependencies = new Map(); // Track module dependencies } /** * Register a module * @param {BaseModule} module - Module to register * @param {Array} [dependencies] - Optional array of module dependencies */ register(module, dependencies = null) { if (!module || !module.id) { console.error('Invalid module - must have an id property'); return; } const alreadyRegistered = this.modules[module.id] === module; // Store the module this.modules[module.id] = module; // Store dependencies if provided, otherwise use module.dependencies if (dependencies) { this.moduleDependencies.set(module.id, dependencies); // Mirror explicit dependencies onto the module instance. if (module.dependencies === undefined) { module.dependencies = [...dependencies]; } } else if (module.dependencies) { // Use the module's own dependencies property this.moduleDependencies.set(module.id, [...module.dependencies]); } else { // No dependencies this.moduleDependencies.set(module.id, []); } if (alreadyRegistered && this.readyPromises[module.id]) { return; } // Create a promise that will resolve when this module is ready this.readyPromises[module.id] = new Promise((resolve) => { // Set up a state change listener for this module document.addEventListener('module:stateChange', (event) => { if (event.detail.moduleId === module.id && (event.detail.state === 'FINISHED' || event.detail.state === 'ERROR')) { resolve(event.detail.state === 'FINISHED'); } }); // Check if already in finished state if (module.state === 'FINISHED') { resolve(true); } else if (module.state === 'ERROR') { resolve(false); } }); } /** * Get a module by id * @param {string} id - Module id * @returns {BaseModule} - The module, or null if not found */ getModule(id) { return this.modules[id] || null; } /** * Get all registered modules * @returns {Object} - Map of modules */ getAllModules() { return this.modules; } /** * Get dependencies for a module * @param {string} id - Module id * @returns {Array} - Array of dependencies */ getDependencies(id) { return this.moduleDependencies.get(id) || []; } /** * Wait for a module to be ready (in FINISHED state) * @param {string} id - Module id to wait for * @param {number} timeout - Optional timeout in ms * @returns {Promise} - Resolves when the module is ready */ waitForModule(id, timeout = null) { if (!this.readyPromises[id]) { return Promise.resolve(false); } if (timeout) { // Add timeout logic return Promise.race([ this.readyPromises[id], new Promise(resolve => setTimeout(() => resolve(false), timeout)) ]); } return this.readyPromises[id]; } /** * Wait for multiple modules to be ready * @param {Array} ids - Array of module ids to wait for * @param {number} timeout - Optional timeout in ms * @returns {Promise} - Resolves when all modules are ready */ waitForModules(ids, timeout = null) { const promises = ids.map(id => this.waitForModule(id, timeout)); return Promise.all(promises); } } // Create and export a singleton instance export const moduleRegistry = new ModuleRegistry(); // Make registry accessible globally window.moduleRegistry = moduleRegistry;