UNPKG

bigbasealpha

Version:

Enterprise-Grade NoSQL Database System with Modular Logger & Offline HSM Security - Complete database platform with professional text-based logging, encryption, caching, indexing, JWT authentication, auto-generated REST API, real-time dashboard, and maste

602 lines (508 loc) 15.6 kB
import { promises as fs, existsSync } from 'fs'; import { join } from 'path'; /** * Configuration Manager for BigBaseAlpha * Handles database configuration and settings */ export class ConfigManager { constructor(initialConfig = {}) { this.config = { ...initialConfig }; this.configPath = null; this.watchers = new Map(); this.changeListeners = []; } async init(initialConfig = {}) { // Set config file path this.configPath = join(this.config.path || './', 'bigbase.config.json'); // Load existing config if available await this.load(); // Merge with initial config this.config = { ...this.config, ...initialConfig }; // Save merged config await this.save(); } /** * Get configuration value */ get(key, defaultValue = undefined) { const keys = key.split('.'); let value = this.config; for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k]; } else { return defaultValue; } } return value; } /** * Set configuration value */ async set(key, value) { const keys = key.split('.'); const lastKey = keys.pop(); let current = this.config; // Navigate to parent object for (const k of keys) { if (!current[k] || typeof current[k] !== 'object') { current[k] = {}; } current = current[k]; } // Set value const oldValue = current[lastKey]; current[lastKey] = value; // Notify listeners this._notifyChange(key, value, oldValue); // Save config await this.save(); } /** * Delete configuration key */ async delete(key) { const keys = key.split('.'); const lastKey = keys.pop(); let current = this.config; // Navigate to parent object for (const k of keys) { if (!current[k] || typeof current[k] !== 'object') { return false; } current = current[k]; } if (lastKey in current) { const oldValue = current[lastKey]; delete current[lastKey]; // Notify listeners this._notifyChange(key, undefined, oldValue); // Save config await this.save(); return true; } return false; } /** * Get all configuration */ getAll() { return { ...this.config }; } /** * Set multiple configuration values */ async setMultiple(values) { const changes = []; for (const [key, value] of Object.entries(values)) { const oldValue = this.get(key); await this.set(key, value); changes.push({ key, value, oldValue }); } return changes; } /** * Reset configuration to defaults */ async reset(defaultConfig = {}) { const oldConfig = { ...this.config }; this.config = { ...defaultConfig }; // Notify listeners of reset this._notifyChange('*', this.config, oldConfig); await this.save(); } /** * Load configuration from file */ async load() { if (!this.configPath || !existsSync(this.configPath)) { return false; } try { const configData = await fs.readFile(this.configPath, 'utf8'); const loadedConfig = JSON.parse(configData); // Validate configuration this._validateConfig(loadedConfig); this.config = { ...this.config, ...loadedConfig }; return true; } catch (error) { console.error('Failed to load configuration:', error); return false; } } /** * Save configuration to file */ async save() { if (!this.configPath) { return false; } try { const configData = JSON.stringify(this.config, null, 2); await fs.writeFile(this.configPath, configData); return true; } catch (error) { console.error('Failed to save configuration:', error); return false; } } /** * Watch for configuration changes */ watch(key, callback) { if (!this.watchers.has(key)) { this.watchers.set(key, []); } this.watchers.get(key).push(callback); // Return unwatch function return () => { const callbacks = this.watchers.get(key); if (callbacks) { const index = callbacks.indexOf(callback); if (index !== -1) { callbacks.splice(index, 1); } if (callbacks.length === 0) { this.watchers.delete(key); } } }; } /** * Add global change listener */ onChange(callback) { this.changeListeners.push(callback); // Return remove listener function return () => { const index = this.changeListeners.indexOf(callback); if (index !== -1) { this.changeListeners.splice(index, 1); } }; } /** * Export configuration */ async export(filePath, format = 'json') { const exportData = this.getAll(); try { switch (format.toLowerCase()) { case 'json': await fs.writeFile(filePath, JSON.stringify(exportData, null, 2)); break; case 'yaml': const yaml = this._convertToYAML(exportData); await fs.writeFile(filePath, yaml); break; case 'env': const env = this._convertToEnv(exportData); await fs.writeFile(filePath, env); break; default: throw new Error(`Unsupported export format: ${format}`); } return true; } catch (error) { console.error('Failed to export configuration:', error); return false; } } /** * Import configuration */ async import(filePath, format = 'json', merge = true) { try { const fileContent = await fs.readFile(filePath, 'utf8'); let importedConfig; switch (format.toLowerCase()) { case 'json': importedConfig = JSON.parse(fileContent); break; case 'yaml': importedConfig = this._parseYAML(fileContent); break; case 'env': importedConfig = this._parseEnv(fileContent); break; default: throw new Error(`Unsupported import format: ${format}`); } // Validate imported configuration this._validateConfig(importedConfig); if (merge) { this.config = { ...this.config, ...importedConfig }; } else { this.config = importedConfig; } await this.save(); return true; } catch (error) { console.error('Failed to import configuration:', error); return false; } } /** * Get configuration schema */ getSchema() { return { path: { type: 'string', description: 'Database storage path', default: './bigbase_data' }, format: { type: 'string', description: 'Storage format', enum: ['json', 'binary', 'hybrid'], default: 'json' }, encryption: { type: 'boolean', description: 'Enable encryption', default: false }, compression: { type: 'boolean', description: 'Enable compression', default: false }, maxMemory: { type: 'string', description: 'Maximum memory usage', pattern: '^\\d+(\\.\\d+)?(B|KB|MB|GB)$', default: '512MB' }, backupInterval: { type: 'number', description: 'Backup interval in milliseconds', minimum: 0, default: 3600000 }, indexing: { type: 'boolean', description: 'Enable indexing', default: true }, caching: { type: 'boolean', description: 'Enable caching', default: true }, auditLog: { type: 'boolean', description: 'Enable audit logging', default: true }, plugins: { type: 'array', description: 'Enabled plugins', items: { oneOf: [ { type: 'string' }, { type: 'object', properties: { name: { type: 'string' }, options: { type: 'object' } }, required: ['name'] } ] }, default: [] } }; } /** * Validate configuration against schema */ validate() { return this._validateConfig(this.config); } // Private methods _validateConfig(config) { const schema = this.getSchema(); const errors = []; for (const [key, rule] of Object.entries(schema)) { const value = config[key]; // Check required fields if (rule.required && (value === undefined || value === null)) { errors.push(`Missing required field: ${key}`); continue; } // Skip validation if value is undefined and not required if (value === undefined) { continue; } // Type validation if (rule.type && typeof value !== rule.type) { if (rule.type === 'array' && !Array.isArray(value)) { errors.push(`Field ${key} must be an array`); } else if (rule.type !== 'array') { errors.push(`Field ${key} must be of type ${rule.type}`); } } // Enum validation if (rule.enum && !rule.enum.includes(value)) { errors.push(`Field ${key} must be one of: ${rule.enum.join(', ')}`); } // Pattern validation if (rule.pattern && typeof value === 'string') { const regex = new RegExp(rule.pattern); if (!regex.test(value)) { errors.push(`Field ${key} does not match required pattern`); } } // Range validation if (typeof value === 'number') { if (rule.minimum !== undefined && value < rule.minimum) { errors.push(`Field ${key} must be at least ${rule.minimum}`); } if (rule.maximum !== undefined && value > rule.maximum) { errors.push(`Field ${key} must be at most ${rule.maximum}`); } } } if (errors.length > 0) { throw new Error(`Configuration validation failed:\n${errors.join('\n')}`); } return true; } _notifyChange(key, newValue, oldValue) { // Notify specific watchers const watchers = this.watchers.get(key) || []; for (const callback of watchers) { try { callback(newValue, oldValue, key); } catch (error) { console.error('Configuration watcher error:', error); } } // Notify global listeners for (const callback of this.changeListeners) { try { callback(key, newValue, oldValue); } catch (error) { console.error('Configuration change listener error:', error); } } } _convertToYAML(obj, indent = 0) { let yaml = ''; const spaces = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { yaml += `${spaces}${key}: null\n`; } else if (typeof value === 'object' && !Array.isArray(value)) { yaml += `${spaces}${key}:\n${this._convertToYAML(value, indent + 1)}`; } else if (Array.isArray(value)) { yaml += `${spaces}${key}:\n`; for (const item of value) { if (typeof item === 'object') { yaml += `${spaces} - ${JSON.stringify(item)}\n`; } else { yaml += `${spaces} - ${item}\n`; } } } else { yaml += `${spaces}${key}: ${value}\n`; } } return yaml; } _convertToEnv(obj, prefix = '') { let env = ''; for (const [key, value] of Object.entries(obj)) { const envKey = prefix ? `${prefix}_${key.toUpperCase()}` : key.toUpperCase(); if (typeof value === 'object' && !Array.isArray(value) && value !== null) { env += this._convertToEnv(value, envKey); } else if (Array.isArray(value)) { env += `${envKey}=${JSON.stringify(value)}\n`; } else { env += `${envKey}=${value}\n`; } } return env; } _parseYAML(yamlContent) { // Simple YAML parser - for production use a proper YAML library const lines = yamlContent.split('\n'); const result = {}; let currentObj = result; const stack = [result]; for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const indent = line.length - line.trimStart().length; const colonIndex = trimmed.indexOf(':'); if (colonIndex === -1) continue; const key = trimmed.substring(0, colonIndex).trim(); const value = trimmed.substring(colonIndex + 1).trim(); // Adjust stack based on indentation while (stack.length > Math.floor(indent / 2) + 1) { stack.pop(); } currentObj = stack[stack.length - 1]; if (value === '') { // Object currentObj[key] = {}; stack.push(currentObj[key]); } else if (value === 'null') { currentObj[key] = null; } else if (value === 'true') { currentObj[key] = true; } else if (value === 'false') { currentObj[key] = false; } else if (!isNaN(value)) { currentObj[key] = parseFloat(value); } else { currentObj[key] = value; } } return result; } _parseEnv(envContent) { const result = {}; const lines = envContent.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const equalIndex = trimmed.indexOf('='); if (equalIndex === -1) continue; const key = trimmed.substring(0, equalIndex); const value = trimmed.substring(equalIndex + 1); // Convert back to nested object const keys = key.toLowerCase().split('_'); let current = result; for (let i = 0; i < keys.length - 1; i++) { if (!current[keys[i]]) { current[keys[i]] = {}; } current = current[keys[i]]; } const lastKey = keys[keys.length - 1]; // Parse value if (value === 'null') { current[lastKey] = null; } else if (value === 'true') { current[lastKey] = true; } else if (value === 'false') { current[lastKey] = false; } else if (!isNaN(value)) { current[lastKey] = parseFloat(value); } else if (value.startsWith('[') || value.startsWith('{')) { try { current[lastKey] = JSON.parse(value); } catch { current[lastKey] = value; } } else { current[lastKey] = value; } } return result; } } export default ConfigManager;