UNPKG

@thinkeloquent/core-configure

Version:

Entity configuration management with deep merging, validation, and entity definitions

232 lines 8.02 kB
import { Ok, Err } from '@thinkeloquent/core-exceptions'; import { EntityConfigSchema, ConfigSource, ConfigurationManagerOptionsSchema, } from './types.js'; import { mergeConfigs, mergeLayers, getDefaultMergeOptions } from './merge-strategies.js'; import { EntityDefinitionRegistry } from './entity-definitions.js'; /** * Entity configuration manager with layered configuration support */ export class EntityConfigurationManager { constructor(options) { this.configLayers = new Map(); this.configCache = new Map(); const validation = ConfigurationManagerOptionsSchema.safeParse(options || {}); if (!validation.success) { throw new Error(`Invalid configuration manager options: ${validation.error.message}`); } this.options = validation.data; this.definitions = new EntityDefinitionRegistry(); } /** * Get entity definition registry */ getDefinitionRegistry() { return this.definitions; } /** * Set configuration for an entity */ setConfig(entityId, entityType, config, source = ConfigSource.RUNTIME) { if (this.options.enableValidation) { const validationResult = this.validateConfig(config); if (!validationResult.valid) { return new Err(new Error(`Configuration validation failed: ${validationResult.errors.join(', ')}`)); } } const key = this.getKey(entityId, entityType); const layers = this.configLayers.get(key) || []; // Add or update layer for this source const priority = this.getSourcePriority(source); const existingLayerIndex = layers.findIndex((l) => l.source === source); const newLayer = { source, priority, config, timestamp: new Date(), }; if (existingLayerIndex >= 0) { layers[existingLayerIndex] = newLayer; } else { layers.push(newLayer); } // Sort by priority (highest first) layers.sort((a, b) => b.priority - a.priority); this.configLayers.set(key, layers); // Invalidate cache this.configCache.delete(key); return new Ok(undefined); } /** * Get merged configuration for an entity */ getConfig(entityId, entityType) { const key = this.getKey(entityId, entityType); // Check cache if (this.options.enableCaching) { const cached = this.configCache.get(key); if (cached) { return new Ok(cached); } } const layers = this.configLayers.get(key); if (!layers || layers.length === 0) { return new Err(new Error(`No configuration found for entity: ${entityId} (${entityType})`)); } // Merge all layers (reverse so lowest priority merges first, highest last) const mergeOptions = getDefaultMergeOptions(this.options.defaultMergeStrategy); const configs = layers.map((layer) => layer.config).reverse(); const merged = mergeLayers(configs, mergeOptions); // Cache result if (this.options.enableCaching) { this.configCache.set(key, merged); } return new Ok(merged); } /** * Get configuration from specific source */ getConfigBySource(entityId, entityType, source) { const key = this.getKey(entityId, entityType); const layers = this.configLayers.get(key); if (!layers) { return new Err(new Error(`No configuration found for entity: ${entityId} (${entityType})`)); } const layer = layers.find((l) => l.source === source); if (!layer) { return new Err(new Error(`No configuration from source ${source} for entity: ${entityId} (${entityType})`)); } return new Ok(layer.config); } /** * Merge additional configuration with existing config */ mergeConfig(entityId, entityType, additionalConfig, options) { const currentResult = this.getConfig(entityId, entityType); if (currentResult.isErr()) { return currentResult; } const mergeOpts = options || getDefaultMergeOptions(this.options.defaultMergeStrategy); const merged = mergeConfigs(currentResult.value, additionalConfig, mergeOpts); return new Ok(merged); } /** * Remove configuration for an entity */ removeConfig(entityId, entityType, source) { const key = this.getKey(entityId, entityType); if (source) { // Remove specific source layer const layers = this.configLayers.get(key); if (!layers) { return new Err(new Error(`No configuration found for entity: ${entityId} (${entityType})`)); } const filteredLayers = layers.filter((l) => l.source !== source); if (filteredLayers.length === layers.length) { return new Err(new Error(`No configuration from source ${source} for entity: ${entityId} (${entityType})`)); } if (filteredLayers.length === 0) { this.configLayers.delete(key); } else { this.configLayers.set(key, filteredLayers); } } else { // Remove all layers this.configLayers.delete(key); } // Invalidate cache this.configCache.delete(key); return new Ok(undefined); } /** * Check if configuration exists for an entity */ hasConfig(entityId, entityType) { const key = this.getKey(entityId, entityType); return this.configLayers.has(key); } /** * Get configuration metadata */ getMetadata(entityId, entityType) { const key = this.getKey(entityId, entityType); const layers = this.configLayers.get(key); if (!layers || layers.length === 0) { return new Err(new Error(`No configuration found for entity: ${entityId} (${entityType})`)); } const sources = layers.map((l) => l.source); const lastUpdated = new Date(Math.max(...layers.map((l) => l.timestamp.getTime()))); const metadata = { entityId, entityType, version: '2.0.0', sources, lastUpdated, }; return new Ok(metadata); } /** * Validate configuration against schema */ validateConfig(config) { const result = EntityConfigSchema.safeParse(config); if (result.success) { return { valid: true, errors: [], warnings: [], }; } const errors = result.error.errors.map((err) => `${err.path.join('.')}: ${err.message}`); return { valid: false, errors, warnings: [], }; } /** * Clear all cached configurations */ clearCache() { this.configCache.clear(); } /** * Clear all configurations */ clear() { this.configLayers.clear(); this.configCache.clear(); } /** * Get all entity keys with configurations */ getAllKeys() { return Array.from(this.configLayers.keys()); } /** * Get count of configured entities */ count() { return this.configLayers.size; } /** * Get source priority for layering */ getSourcePriority(source) { const priorities = { [ConfigSource.DEFAULT]: 1, [ConfigSource.FILESYSTEM]: 2, [ConfigSource.CONTROL_PLANE]: 3, [ConfigSource.RUNTIME]: 4, }; return priorities[source]; } /** * Generate unique key for entity */ getKey(entityId, entityType) { return `${entityType}:${entityId}`; } } //# sourceMappingURL=configuration-manager.js.map