@thinkeloquent/core-configure
Version:
Entity configuration management with deep merging, validation, and entity definitions
232 lines • 8.02 kB
JavaScript
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