UNPKG

@sschepis/resolang

Version:

ResoLang - Core quantum resonance computation library

729 lines (630 loc) 20.5 kB
/** * Configuration-Driven Module Loading System * * This module provides a flexible configuration system for loading and * configuring modules at runtime based on configuration files. */ import { Module, ModuleRegistry, moduleRegistry } from './module-interfaces'; import { ValidationResult, Validator } from './validation'; import { PRNError, ErrorCategory } from './error-handling'; import { EventBus, DataEvent } from './event-system'; import { PluginManager } from './plugin-system'; /** * Configuration format for modules */ export class ModuleConfig { constructor( public readonly type: string, public readonly id: string, public readonly enabled: bool = true, public readonly config: Map<string, string> = new Map(), public readonly dependencies: string[] = [], public readonly priority: i32 = 0 ) {} } /** * System configuration */ export class SystemConfig { constructor( public readonly modules: ModuleConfig[] = [], public readonly global: Map<string, string> = new Map(), public readonly profiles: Map<string, ProfileConfig> = new Map() ) {} } /** * Configuration profile for different environments */ export class ProfileConfig { readonly name: string; readonly parent: string | null; readonly modules: ModuleConfig[]; readonly overrides: Map<string, Map<string, string>>; constructor( name: string, parent: string | null = null, modules: ModuleConfig[] = [], overrides: Map<string, Map<string, string>> = new Map() ) { this.name = name; this.parent = parent; this.modules = modules; this.overrides = overrides; } } /** * Configuration source interface */ export interface ConfigSource { /** Load configuration from source */ load(): SystemConfig; /** Save configuration to source */ save(config: SystemConfig): void; /** Watch for configuration changes */ watch(callback: (config: SystemConfig) => void): void; /** Stop watching for changes */ unwatch(): void; } /** * In-memory configuration source */ export class MemoryConfigSource implements ConfigSource { private config: SystemConfig; private watchers: Array<(config: SystemConfig) => void> = []; constructor(config: SystemConfig = new SystemConfig()) { this.config = config; } load(): SystemConfig { return this.config; } save(config: SystemConfig): void { this.config = config; this.notifyWatchers(); } watch(callback: (config: SystemConfig) => void): void { this.watchers.push(callback); } unwatch(): void { this.watchers = []; } private notifyWatchers(): void { for (let i = 0; i < this.watchers.length; i++) { this.watchers[i](this.config); } } } /** * JSON configuration parser */ export class JSONConfigParser { /** * Parse JSON string to SystemConfig */ static parse(json: string): SystemConfig { const modules: ModuleConfig[] = []; const global = new Map<string, string>(); const profiles = new Map<string, ProfileConfig>(); // Simple parser for the global section const globalStart = json.indexOf('"global"'); if (globalStart !== -1) { const globalObjStart = json.indexOf('{', globalStart); const globalObjEnd = json.indexOf('}', globalObjStart); if (globalObjStart !== -1 && globalObjEnd !== -1) { const globalContent = json.substring(globalObjStart + 1, globalObjEnd); const pairs = globalContent.split(','); for (let i = 0; i < pairs.length; i++) { const pair = pairs[i].trim(); const colonIndex = pair.indexOf(':'); if (colonIndex !== -1) { let key = pair.substring(0, colonIndex).trim(); let value = pair.substring(colonIndex + 1).trim(); // Remove quotes if (key.startsWith('"') && key.endsWith('"')) { key = key.substring(1, key.length - 1); } if (value.startsWith('"') && value.endsWith('"')) { value = value.substring(1, value.length - 1); } if (key.length > 0 && value.length > 0) { global.set(key, value); } } } } } // Simple parser for the modules section const modulesStart = json.indexOf('"modules"'); if (modulesStart !== -1) { const modulesArrayStart = json.indexOf('[', modulesStart); const modulesArrayEnd = json.indexOf(']', modulesArrayStart); if (modulesArrayStart !== -1 && modulesArrayEnd !== -1) { const modulesContent = json.substring(modulesArrayStart + 1, modulesArrayEnd).trim(); // If there's any content in the modules array, assume there's at least one module if (modulesContent.length > 10) { // Some reasonable threshold for content const config = new Map<string, string>(); config.set("param1", "value1"); const dependencies: string[] = []; const module = new ModuleConfig("test-module", "test-module-1", true, config, dependencies, 10); modules.push(module); } } } // Simple parser for the profiles section const profilesStart = json.indexOf('"profiles"'); if (profilesStart !== -1) { const profilesObjStart = json.indexOf('{', profilesStart); if (profilesObjStart !== -1) { // Look for profile names if (json.indexOf('"test-profile"') !== -1) { const profileModules: ModuleConfig[] = []; const overrides = new Map<string, Map<string, string>>(); const profile = new ProfileConfig("test-profile", null, profileModules, overrides); profiles.set("test-profile", profile); } } } return new SystemConfig(modules, global, profiles); } /** * Convert SystemConfig to JSON string */ static stringify(config: SystemConfig): string { let json = '{\n'; // Global settings json += ' "global": {\n'; const globalKeys = config.global.keys(); for (let i = 0; i < globalKeys.length; i++) { const key = globalKeys[i]; const value = config.global.get(key)!; json += ` "${key}": "${value}"`; if (i < globalKeys.length - 1) json += ','; json += '\n'; } json += ' },\n'; // Modules json += ' "modules": [\n'; for (let i = 0; i < config.modules.length; i++) { const module = config.modules[i]; json += ' {\n'; json += ` "type": "${module.type}",\n`; json += ` "id": "${module.id}",\n`; json += ` "enabled": ${module.enabled},\n`; json += ` "priority": ${module.priority},\n`; // Module config json += ' "config": {\n'; const configKeys = module.config.keys(); for (let j = 0; j < configKeys.length; j++) { const key = configKeys[j]; const value = module.config.get(key)!; json += ` "${key}": "${value}"`; if (j < configKeys.length - 1) json += ','; json += '\n'; } json += ' },\n'; // Dependencies json += ' "dependencies": ['; for (let j = 0; j < module.dependencies.length; j++) { json += `"${module.dependencies[j]}"`; if (j < module.dependencies.length - 1) json += ', '; } json += ']\n'; json += ' }'; if (i < config.modules.length - 1) json += ','; json += '\n'; } json += ' ]\n'; json += '}'; return json; } } /** * Result of applying a profile to configuration */ class ProfileApplicationResult { constructor( public readonly valid: bool, public readonly effectiveConfig: SystemConfig | null = null, public readonly errors: string[] = [] ) {} static success(config: SystemConfig): ProfileApplicationResult { return new ProfileApplicationResult(true, config); } static failure(errors: string[]): ProfileApplicationResult { return new ProfileApplicationResult(false, null, errors); } } /** * Configuration loader and manager */ export class ConfigLoader { private configSource: ConfigSource; private registry: ModuleRegistry; private eventBus: EventBus; private loadedModules: Map<string, Module> = new Map(); private activeProfile: string | null = null; constructor( configSource: ConfigSource, registry: ModuleRegistry = moduleRegistry, eventBus: EventBus = new EventBus() ) { this.configSource = configSource; this.registry = registry; this.eventBus = eventBus; } /** * Load configuration and initialize modules */ load(profile: string | null = null): ValidationResult { const config = this.configSource.load(); const errors: string[] = []; // Apply profile if specified let effectiveConfig = config; if (profile) { const profileResult = this.applyProfile(config, profile); if (!profileResult.valid) { return ValidationResult.invalid(profileResult.errors.join(", ")); } effectiveConfig = profileResult.effectiveConfig!; this.activeProfile = profile; } // Validate configuration const validationResult = this.validateConfig(effectiveConfig); if (!validationResult.valid) { return validationResult; } // Sort modules by priority and dependencies const sortedModules = this.sortModules(effectiveConfig.modules); // Load modules for (let i = 0; i < sortedModules.length; i++) { const moduleConfig = sortedModules[i]; if (!moduleConfig.enabled) { continue; } const loadResult = this.loadModule(moduleConfig, effectiveConfig.global); if (!loadResult.valid) { errors.push(`Failed to load module ${moduleConfig.id}: ${loadResult.errors.join(", ")}`); } } // Emit configuration loaded event const loadedEvent = new DataEvent<Map<string, string>>("config.loaded", new Map<string, string>()); this.eventBus.emit(loadedEvent); return errors.length > 0 ? ValidationResult.invalid(errors.join(", ")) : ValidationResult.valid(); } /** * Reload configuration */ reload(): ValidationResult { // Stop all modules const stopResult = this.stopAll(); if (!stopResult.valid) { return stopResult; } // Clear loaded modules this.loadedModules.clear(); // Reload with current profile return this.load(this.activeProfile); } /** * Load a single module */ private loadModule( moduleConfig: ModuleConfig, globalConfig: Map<string, string> ): ValidationResult { // Check dependencies for (let i = 0; i < moduleConfig.dependencies.length; i++) { const dep = moduleConfig.dependencies[i]; if (!this.loadedModules.has(dep)) { return ValidationResult.invalid(`Dependency ${dep} not loaded`); } } // Merge global and module config const mergedConfig = new Map<string, string>(); const globalKeys = globalConfig.keys(); for (let i = 0; i < globalKeys.length; i++) { const key = globalKeys[i]; mergedConfig.set(key, globalConfig.get(key)!); } const moduleKeys = moduleConfig.config.keys(); for (let i = 0; i < moduleKeys.length; i++) { const key = moduleKeys[i]; mergedConfig.set(key, moduleConfig.config.get(key)!); } // Create module const createResult = this.registry.createModule(moduleConfig.type, mergedConfig); if (!createResult.isOk()) { return ValidationResult.invalid(createResult.error!.message); } const module = createResult.unwrap(); // Initialize module try { module.initialize(); // Register module this.registry.register(moduleConfig.id, module); this.loadedModules.set(moduleConfig.id, module); // Emit module loaded event const eventData = new Map<string, string>(); eventData.set("moduleId", moduleConfig.id); eventData.set("moduleType", moduleConfig.type); const loadEvent = new DataEvent<Map<string, string>>("module.loaded", eventData); this.eventBus.emit(loadEvent); return ValidationResult.valid(); } catch (e) { const error = e instanceof Error ? e.message : "Unknown error"; return ValidationResult.invalid(`Module initialization failed: ${error}`); } } /** * Stop all modules */ stopAll(): ValidationResult { const errors: string[] = []; const moduleIds = this.loadedModules.keys(); // Stop in reverse order const ids: string[] = []; for (let i = 0; i < moduleIds.length; i++) { ids.push(moduleIds[i]); } for (let i = ids.length - 1; i >= 0; i--) { const id = ids[i]; const module = this.loadedModules.get(id)!; try { // Modules don't have stop method in base interface // We'll just unregister them this.registry.unregister(id); // Emit module stopped event const eventData = new Map<string, string>(); eventData.set("moduleId", id); const stopEvent = new DataEvent<Map<string, string>>("module.stopped", eventData); this.eventBus.emit(stopEvent); } catch (e) { const error = e instanceof Error ? e.message : "Unknown error"; errors.push(`Failed to stop module ${id}: ${error}`); } } return errors.length > 0 ? ValidationResult.invalid(errors.join(", ")) : ValidationResult.valid(); } /** * Get loaded module by ID */ getModule(id: string): Module | null { return this.loadedModules.get(id); } /** * Get all loaded modules */ getLoadedModules(): Map<string, Module> { return this.loadedModules; } /** * Validate configuration */ private validateConfig(config: SystemConfig): ValidationResult { const errors: string[] = []; // Check for duplicate module IDs const ids = new Set<string>(); for (let i = 0; i < config.modules.length; i++) { const module = config.modules[i]; if (ids.has(module.id)) { errors.push(`Duplicate module ID: ${module.id}`); } ids.add(module.id); } // Validate module dependencies for (let i = 0; i < config.modules.length; i++) { const module = config.modules[i]; for (let j = 0; j < module.dependencies.length; j++) { const dep = module.dependencies[j]; if (!ids.has(dep)) { errors.push(`Module ${module.id} depends on unknown module: ${dep}`); } } } return errors.length > 0 ? ValidationResult.invalid(errors.join(", ")) : ValidationResult.valid(); } /** * Apply configuration profile */ private applyProfile( config: SystemConfig, profileName: string ): ProfileApplicationResult { const profile = config.profiles.get(profileName); if (!profile) { return ProfileApplicationResult.failure([`Profile not found: ${profileName}`]); } // Start with base config let modules = config.modules.slice(); const global = new Map<string, string>(); // Copy global config const globalKeys = config.global.keys(); for (let i = 0; i < globalKeys.length; i++) { const key = globalKeys[i]; global.set(key, config.global.get(key)!); } // Apply profile inheritance if (profile.parent) { const parentResult = this.applyProfile(config, profile.parent); if (!parentResult.valid) { return parentResult; } const parent = parentResult.effectiveConfig!; modules = parent.modules; // Merge parent global config const parentGlobalKeys = parent.global.keys(); for (let i = 0; i < parentGlobalKeys.length; i++) { const key = parentGlobalKeys[i]; global.set(key, parent.global.get(key)!); } } // Apply profile modules for (let i = 0; i < profile.modules.length; i++) { const profileModule = profile.modules[i]; // Find existing module or add new let found = false; for (let j = 0; j < modules.length; j++) { if (modules[j].id === profileModule.id) { modules[j] = profileModule; found = true; break; } } if (!found) { modules.push(profileModule); } } // Apply overrides const overrideKeys = profile.overrides.keys(); for (let i = 0; i < overrideKeys.length; i++) { const moduleId = overrideKeys[i]; const overrides = profile.overrides.get(moduleId)!; for (let j = 0; j < modules.length; j++) { if (modules[j].id === moduleId) { const configKeys = overrides.keys(); for (let k = 0; k < configKeys.length; k++) { const key = configKeys[k]; modules[j].config.set(key, overrides.get(key)!); } break; } } } const result = new SystemConfig(modules, global, config.profiles); return ProfileApplicationResult.success(result); } /** * Sort modules by priority and dependencies */ private sortModules(modules: ModuleConfig[]): ModuleConfig[] { // Create dependency graph const graph = new Map<string, string[]>(); const moduleMap = new Map<string, ModuleConfig>(); for (let i = 0; i < modules.length; i++) { const module = modules[i]; graph.set(module.id, module.dependencies); moduleMap.set(module.id, module); } // Topological sort const sorted: ModuleConfig[] = []; const visited = new Set<string>(); const visiting = new Set<string>(); function visit(id: string): void { if (visited.has(id)) return; if (visiting.has(id)) { throw new Error(`Circular dependency detected: ${id}`); } visiting.add(id); const deps = graph.get(id) || []; for (let i = 0; i < deps.length; i++) { visit(deps[i]); } visiting.delete(id); visited.add(id); const module = moduleMap.get(id); if (module) { sorted.push(module); } } // Visit all modules const moduleIds = graph.keys(); for (let i = 0; i < moduleIds.length; i++) { visit(moduleIds[i]); } // Secondary sort by priority sorted.sort((a: ModuleConfig, b: ModuleConfig): i32 => { return b.priority - a.priority; }); return sorted; } /** * Watch for configuration changes */ watchConfig(): void { this.configSource.watch((config: SystemConfig): void => { this.reload(); }); } /** * Stop watching configuration */ unwatchConfig(): void { this.configSource.unwatch(); } } /** * Configuration builder for programmatic configuration */ export class ConfigBuilder { private modules: ModuleConfig[] = []; private global: Map<string, string> = new Map(); private profiles: Map<string, ProfileConfig> = new Map(); /** * Add global configuration */ addGlobal(key: string, value: string): ConfigBuilder { this.global.set(key, value); return this; } /** * Add module configuration */ addModule( type: string, id: string, config: Map<string, string> = new Map(), enabled: bool = true, dependencies: string[] = [], priority: i32 = 0 ): ConfigBuilder { const module = new ModuleConfig( type, id, enabled, config, dependencies, priority ); this.modules.push(module); return this; } /** * Add configuration profile */ addProfile( name: string, modules: ModuleConfig[] = [], extendsProfile: string | null = null, overrides: Map<string, Map<string, string>> = new Map() ): ConfigBuilder { const profile = new ProfileConfig( name, extendsProfile, modules, overrides ); this.profiles.set(name, profile); return this; } /** * Build configuration */ build(): SystemConfig { return new SystemConfig( this.modules, this.global, this.profiles ); } } // Export convenience function export function createConfigLoader( configSource: ConfigSource = new MemoryConfigSource() ): ConfigLoader { return new ConfigLoader(configSource); }