task-engine-ai-core
Version:
Revolutionary AI-driven task management system with complete transformation trilogy: Frontend v0.1.0, Backend v0.2.0, CLI v0.3.0 - Enterprise-grade performance with 95% improvements
364 lines (314 loc) • 10.9 kB
JavaScript
/**
* Configuration Loader for Task Engine AI Core
*
* Handles loading, merging, and validating configuration files
* for the task-engine-ai-core package.
*
* @version 0.3.0
* @author Task Engine AI Team
*/
import { readFileSync, existsSync } from 'fs';
import { join, resolve } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Configuration Loader Class
*/
export class ConfigLoader {
constructor(options = {}) {
this.options = {
environment: process.env.NODE_ENV || 'default',
configDir: options.configDir || join(process.cwd(), 'config'),
packageConfigDir: join(__dirname, '../../config'),
validateSchema: options.validateSchema !== false,
...options
};
this.config = null;
this.schema = null;
}
/**
* Load configuration based on environment
*/
async load() {
try {
// Load default configuration
const defaultConfig = this.loadConfigFile('default.json');
// Load environment-specific configuration
const envConfig = this.loadConfigFile(`${this.options.environment}.json`);
// Load user configuration if exists
const userConfig = this.loadConfigFile('user.json', false);
// Load local configuration if exists
const localConfig = this.loadConfigFile('local.json', false);
// Merge configurations (later configs override earlier ones)
this.config = this.mergeConfigs(
defaultConfig,
envConfig,
userConfig,
localConfig
);
// Apply environment variables
this.applyEnvironmentVariables();
// Validate configuration if schema validation is enabled
if (this.options.validateSchema) {
await this.validateConfiguration();
}
// Set computed values
this.setComputedValues();
return this.config;
} catch (error) {
throw new Error(`Failed to load configuration: ${error.message}`);
}
}
/**
* Load a specific configuration file
*/
loadConfigFile(filename, required = true) {
const paths = [
join(this.options.configDir, filename),
join(this.options.packageConfigDir, filename)
];
for (const configPath of paths) {
if (existsSync(configPath)) {
try {
const content = readFileSync(configPath, 'utf8');
return JSON.parse(content);
} catch (error) {
throw new Error(`Failed to parse config file ${configPath}: ${error.message}`);
}
}
}
if (required) {
throw new Error(`Required configuration file not found: ${filename}`);
}
return {};
}
/**
* Merge multiple configuration objects
*/
mergeConfigs(...configs) {
return configs.reduce((merged, config) => {
if (!config || typeof config !== 'object') {
return merged;
}
return this.deepMerge(merged, config);
}, {});
}
/**
* Deep merge two objects
*/
deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (this.isObject(source[key]) && this.isObject(target[key])) {
result[key] = this.deepMerge(target[key], source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
}
/**
* Check if value is an object
*/
isObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/**
* Apply environment variables to configuration
*/
applyEnvironmentVariables() {
const envMappings = {
'TASK_ENGINE_PORT': 'architectures.backend.port',
'TASK_ENGINE_HOST': 'architectures.backend.host',
'TASK_ENGINE_DB_TYPE': 'architectures.backend.database.type',
'TASK_ENGINE_DB_HOST': 'architectures.backend.database.host',
'TASK_ENGINE_DB_PORT': 'architectures.backend.database.port',
'TASK_ENGINE_DB_NAME': 'architectures.backend.database.database',
'TASK_ENGINE_REDIS_HOST': 'architectures.backend.cache.host',
'TASK_ENGINE_REDIS_PORT': 'architectures.backend.cache.port',
'TASK_ENGINE_LOG_LEVEL': 'taskEngine.logging.level',
'TASK_ENGINE_DEBUG': 'taskEngine.debug',
'ANTHROPIC_API_KEY': 'ai.providers.anthropic.apiKey',
'OPENAI_API_KEY': 'ai.providers.openai.apiKey',
'MCP_PORT': 'mcp.server.port',
'MCP_HOST': 'mcp.server.host'
};
for (const [envVar, configPath] of Object.entries(envMappings)) {
const envValue = process.env[envVar];
if (envValue !== undefined) {
this.setNestedValue(this.config, configPath, this.parseEnvValue(envValue));
}
}
}
/**
* Set nested value in object using dot notation
*/
setNestedValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
}
/**
* Parse environment variable value to appropriate type
*/
parseEnvValue(value) {
// Boolean values
if (value.toLowerCase() === 'true') return true;
if (value.toLowerCase() === 'false') return false;
// Numeric values
if (/^\d+$/.test(value)) return parseInt(value, 10);
if (/^\d+\.\d+$/.test(value)) return parseFloat(value);
// String values
return value;
}
/**
* Validate configuration against schema
*/
async validateConfiguration() {
try {
if (!this.schema) {
this.schema = this.loadConfigFile('schema.json');
}
// Basic validation - in a real implementation, you'd use a JSON schema validator
this.validateRequired();
this.validateTypes();
this.validateRanges();
} catch (error) {
throw new Error(`Configuration validation failed: ${error.message}`);
}
}
/**
* Validate required fields
*/
validateRequired() {
const required = ['taskEngine', 'architectures'];
for (const field of required) {
if (!this.config[field]) {
throw new Error(`Required configuration field missing: ${field}`);
}
}
}
/**
* Validate data types
*/
validateTypes() {
// Validate ports are numbers
const ports = [
'architectures.frontend.port',
'architectures.backend.port',
'mcp.server.port'
];
for (const portPath of ports) {
const port = this.getNestedValue(this.config, portPath);
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {
throw new Error(`Invalid port number at ${portPath}: ${port}`);
}
}
}
/**
* Validate value ranges
*/
validateRanges() {
// Validate AI temperature
const temperature = this.getNestedValue(this.config, 'ai.providers.anthropic.temperature');
if (temperature !== undefined && (temperature < 0 || temperature > 2)) {
throw new Error(`AI temperature must be between 0 and 2: ${temperature}`);
}
}
/**
* Get nested value from object using dot notation
*/
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined;
}, obj);
}
/**
* Set computed values based on configuration
*/
setComputedValues() {
// Set default database path for SQLite
if (this.config.architectures?.backend?.database?.type === 'sqlite' &&
!this.config.architectures.backend.database.path) {
this.config.architectures.backend.database.path =
`data/${this.options.environment}-tasks.db`;
}
// Set default log file path
if (this.config.taskEngine?.logging?.file && !this.config.taskEngine.logging.file.path) {
this.config.taskEngine.logging.file.path =
`logs/task-engine-${this.options.environment}.log`;
}
// Set computed limits based on environment
if (this.options.environment === 'development') {
this.config.limits = {
...this.config.limits,
maxTasks: Math.min(this.config.limits?.maxTasks || 1000, 1000),
maxConcurrentConnections: Math.min(this.config.limits?.maxConcurrentConnections || 100, 100)
};
}
}
/**
* Get current configuration
*/
getConfig() {
if (!this.config) {
throw new Error('Configuration not loaded. Call load() first.');
}
return this.config;
}
/**
* Get configuration value by path
*/
get(path, defaultValue = undefined) {
const value = this.getNestedValue(this.getConfig(), path);
return value !== undefined ? value : defaultValue;
}
/**
* Check if configuration has a specific path
*/
has(path) {
return this.getNestedValue(this.getConfig(), path) !== undefined;
}
/**
* Reload configuration
*/
async reload() {
this.config = null;
return await this.load();
}
}
/**
* Default configuration loader instance
*/
export const configLoader = new ConfigLoader();
/**
* Load configuration with options
*/
export async function loadConfig(options = {}) {
const loader = new ConfigLoader(options);
return await loader.load();
}
/**
* Get configuration value
*/
export function getConfig(path, defaultValue) {
return configLoader.get(path, defaultValue);
}
/**
* Check if configuration has path
*/
export function hasConfig(path) {
return configLoader.has(path);
}
export default ConfigLoader;