UNPKG

adaptus2-orm

Version:

A multi-database ORM supporting MySQL, PostgreSQL, MongoDB, and Snowflake

400 lines (346 loc) 15.1 kB
// src/utils/ConfigManager.js const path = require('path'); const fs = require('fs'); class ConfigManager { constructor() { this.configurations = new Map(); this.defaultConfigs = { mysql: { host: process.env.MYSQL_HOST || 'localhost', port: process.env.MYSQL_PORT || 3306, user: process.env.MYSQL_USER || 'root', password: process.env.MYSQL_PASSWORD || '', database: process.env.MYSQL_DATABASE || 'test', connectionLimit: process.env.MYSQL_CONNECTION_LIMIT || 10, connectTimeout: 60000, acquireTimeout: 60000, timeout: 60000, waitForConnections: true, queueLimit: 0 }, postgresql: { host: process.env.POSTGRES_HOST || 'localhost', port: process.env.POSTGRES_PORT || 5432, user: process.env.POSTGRES_USER || 'postgres', password: process.env.POSTGRES_PASSWORD || '', database: process.env.POSTGRES_DATABASE || 'test', max: process.env.POSTGRES_MAX_CONNECTIONS || 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 60000, query_timeout: 60000, application_name: 'nodejs-orm' }, mongodb: { host: process.env.MONGODB_HOST || 'localhost', port: process.env.MONGODB_PORT || 27017, user: process.env.MONGODB_USER || '', password: process.env.MONGODB_PASSWORD || '', database: process.env.MONGODB_DATABASE || 'test', uri: process.env.MONGODB_URI || null, maxPoolSize: process.env.MONGODB_MAX_POOL_SIZE || 10, minPoolSize: 0, serverSelectionTimeoutMS: 30000, socketTimeoutMS: 30000, authSource: process.env.MONGODB_AUTH_SOURCE || null }, snowflake: { account: process.env.SNOWFLAKE_ACCOUNT || '', user: process.env.SNOWFLAKE_USER || '', password: process.env.SNOWFLAKE_PASSWORD || '', warehouse: process.env.SNOWFLAKE_WAREHOUSE || '', database: process.env.SNOWFLAKE_DATABASE || '', schema: process.env.SNOWFLAKE_SCHEMA || 'PUBLIC', queryTimeout: 120000, sessionParameters: { QUERY_TIMEOUT: 120000, TRANSACTION_DEFAULT_ISOLATION_LEVEL: 'READ COMMITTED' } } }; } // Load configuration from environment variables or config file loadConfig(configPath) { if (configPath && fs.existsSync(configPath)) { try { const config = require(path.resolve(configPath)); // Merge with default configurations for (const [type, typeConfig] of Object.entries(config)) { if (this.defaultConfigs[type]) { this.defaultConfigs[type] = { ...this.defaultConfigs[type], ...typeConfig }; } } return true; } catch (error) { console.error(`Error loading config file: ${error.message}`); return false; } } return false; } // Get configuration for a specific database type getConfig(type, overrides = {}) { const normalizedType = type.toLowerCase(); const baseConfig = this.defaultConfigs[normalizedType]; if (!baseConfig) { throw new Error(`Configuration for database type '${type}' not found`); } // Apply overrides const finalConfig = { ...baseConfig, ...overrides, type: normalizedType }; // Validate configuration this.validateConfig(finalConfig); // Cache the configuration const configKey = this._generateConfigKey(finalConfig); this.configurations.set(configKey, finalConfig); return finalConfig; } // Validate configuration based on database type validateConfig(config) { const { type } = config; switch (type) { case 'mysql': this._validateMySQLConfig(config); break; case 'postgresql': this._validatePostgreSQLConfig(config); break; case 'mongodb': this._validateMongoDBConfig(config); break; case 'snowflake': this._validateSnowflakeConfig(config); break; default: throw new Error(`Unsupported database type: ${type}`); } } _validateMySQLConfig(config) { const required = ['host', 'user', 'database']; this._checkRequiredFields(config, required, 'MySQL'); if (config.port && (config.port < 1 || config.port > 65535)) { throw new Error('MySQL port must be between 1 and 65535'); } if (config.connectionLimit && config.connectionLimit < 1) { throw new Error('MySQL connectionLimit must be at least 1'); } } _validatePostgreSQLConfig(config) { const required = ['host', 'user', 'database']; this._checkRequiredFields(config, required, 'PostgreSQL'); if (config.port && (config.port < 1 || config.port > 65535)) { throw new Error('PostgreSQL port must be between 1 and 65535'); } if (config.max && config.max < 1) { throw new Error('PostgreSQL max connections must be at least 1'); } } _validateMongoDBConfig(config) { if (!config.uri) { const required = ['host', 'database']; this._checkRequiredFields(config, required, 'MongoDB'); if (config.port && (config.port < 1 || config.port > 65535)) { throw new Error('MongoDB port must be between 1 and 65535'); } } if (config.maxPoolSize && config.maxPoolSize < 1) { throw new Error('MongoDB maxPoolSize must be at least 1'); } } _validateSnowflakeConfig(config) { const required = ['account', 'user', 'password', 'warehouse', 'database']; this._checkRequiredFields(config, required, 'Snowflake'); } _checkRequiredFields(config, required, databaseType) { for (const field of required) { if (!config[field]) { throw new Error(`${databaseType} configuration requires '${field}' field`); } } } // Build connection string for databases that support it buildConnectionString(config) { const { type } = config; switch (type) { case 'mysql': return this._buildMySQLConnectionString(config); case 'postgresql': return this._buildPostgreSQLConnectionString(config); case 'mongodb': return this._buildMongoDBConnectionString(config); case 'snowflake': return this._buildSnowflakeConnectionString(config); default: throw new Error(`Connection string builder not implemented for ${type}`); } } _buildMySQLConnectionString(config) { const { user, password, host, port, database } = config; const auth = password ? `${user}:${password}` : user; return `mysql://${auth}@${host}:${port}/${database}`; } _buildPostgreSQLConnectionString(config) { const { user, password, host, port, database } = config; const auth = password ? `${user}:${password}` : user; return `postgresql://${auth}@${host}:${port}/${database}`; } _buildMongoDBConnectionString(config) { if (config.uri) { return config.uri; } const { user, password, host, port, database } = config; let auth = ''; if (user) { auth = password ? `${user}:${password}@` : `${user}@`; } return `mongodb://${auth}${host}:${port}/${database}`; } _buildSnowflakeConnectionString(config) { const { account, user, password, warehouse, database, schema } = config; return `snowflake://${user}:${password}@${account}.snowflakecomputing.com/?warehouse=${warehouse}&database=${database}&schema=${schema}`; } // Create configuration from connection string parseConnectionString(connectionString) { const url = new URL(connectionString); const protocol = url.protocol.replace(':', ''); let config = { type: protocol }; // Extract common parts config.host = url.hostname; config.port = url.port ? parseInt(url.port) : undefined; config.user = url.username || undefined; config.password = url.password || undefined; config.database = url.pathname.slice(1); // Remove leading slash // Parse query parameters const params = new URLSearchParams(url.search); switch (protocol) { case 'mysql': config = { ...this.defaultConfigs.mysql, ...config }; break; case 'postgresql': config = { ...this.defaultConfigs.postgresql, ...config }; break; case 'mongodb': config = { ...this.defaultConfigs.mongodb, ...config, uri: connectionString }; if (params.get('authSource')) { config.authSource = params.get('authSource'); } break; case 'snowflake': config = { ...this.defaultConfigs.snowflake, ...config }; config.account = url.hostname.split('.')[0]; if (params.get('warehouse')) { config.warehouse = params.get('warehouse'); } if (params.get('schema')) { config.schema = params.get('schema'); } break; default: throw new Error(`Unsupported protocol: ${protocol}`); } this.validateConfig(config); return config; } // Create multiple configurations for database environments createEnvironmentConfigs(environment = 'development') { const configs = {}; for (const type of ['mysql', 'postgresql', 'mongodb', 'snowflake']) { try { // Try to get environment-specific config const envPrefix = environment.toUpperCase(); const envConfig = this._getEnvironmentConfig(type, envPrefix); if (envConfig) { configs[type] = this.getConfig(type, envConfig); } } catch (error) { console.warn(`Could not create ${type} config for ${environment}: ${error.message}`); } } return configs; } _getEnvironmentConfig(type, envPrefix) { const upperType = type.toUpperCase(); const config = {}; switch (type) { case 'mysql': config.host = process.env[`${envPrefix}_MYSQL_HOST`]; config.port = process.env[`${envPrefix}_MYSQL_PORT`]; config.user = process.env[`${envPrefix}_MYSQL_USER`]; config.password = process.env[`${envPrefix}_MYSQL_PASSWORD`]; config.database = process.env[`${envPrefix}_MYSQL_DATABASE`]; break; case 'postgresql': config.host = process.env[`${envPrefix}_POSTGRES_HOST`]; config.port = process.env[`${envPrefix}_POSTGRES_PORT`]; config.user = process.env[`${envPrefix}_POSTGRES_USER`]; config.password = process.env[`${envPrefix}_POSTGRES_PASSWORD`]; config.database = process.env[`${envPrefix}_POSTGRES_DATABASE`]; break; case 'mongodb': config.uri = process.env[`${envPrefix}_MONGODB_URI`]; config.host = process.env[`${envPrefix}_MONGODB_HOST`]; config.port = process.env[`${envPrefix}_MONGODB_PORT`]; config.user = process.env[`${envPrefix}_MONGODB_USER`]; config.password = process.env[`${envPrefix}_MONGODB_PASSWORD`]; config.database = process.env[`${envPrefix}_MONGODB_DATABASE`]; break; case 'snowflake': config.account = process.env[`${envPrefix}_SNOWFLAKE_ACCOUNT`]; config.user = process.env[`${envPrefix}_SNOWFLAKE_USER`]; config.password = process.env[`${envPrefix}_SNOWFLAKE_PASSWORD`]; config.warehouse = process.env[`${envPrefix}_SNOWFLAKE_WAREHOUSE`]; config.database = process.env[`${envPrefix}_SNOWFLAKE_DATABASE`]; config.schema = process.env[`${envPrefix}_SNOWFLAKE_SCHEMA`]; break; } // Remove undefined values Object.keys(config).forEach(key => { if (config[key] === undefined) { delete config[key]; } }); return Object.keys(config).length > 0 ? config : null; } _generateConfigKey(config) { const { type, host, port, database, user } = config; return `${type}://${user}@${host}:${port}/${database}`; } // Save configuration to file saveConfig(config, filePath) { try { const existingConfig = fs.existsSync(filePath) ? require(path.resolve(filePath)) : {}; existingConfig[config.type] = { ...existingConfig[config.type], ...this._sanitizeConfig(config) }; fs.writeFileSync(filePath, JSON.stringify(existingConfig, null, 2)); return true; } catch (error) { console.error(`Error saving config to file: ${error.message}`); return false; } } _sanitizeConfig(config) { // Remove sensitive data when saving to file const sanitized = { ...config }; delete sanitized.password; return sanitized; } // Get all cached configurations getAllCached() { return Array.from(this.configurations.entries()).map(([key, config]) => ({ key, config })); } // Clear cached configurations clearCache() { this.configurations.clear(); } } module.exports = ConfigManager;