UNPKG

@bhagat-surya-dev/dashchat-database-manager

Version:

AI-powered database schema analysis and management library

775 lines (769 loc) โ€ข 39.5 kB
"use strict"; /** * Enhanced DatabaseManager with comprehensive error handling and better debugging */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DatabaseManager = void 0; const dotenv = __importStar(require("dotenv")); const perf_hooks_1 = require("perf_hooks"); const cerebras_cloud_sdk_1 = __importDefault(require("@cerebras/cerebras_cloud_sdk")); const robust_json_parser_1 = require("./utils/robust_json_parser"); const index_1 = require("./utils/db-handlers/index"); dotenv.config(); class DatabaseManager { constructor(options) { this.DEFAULT_TIMEOUT = 30000; // 30 seconds console.log('๐Ÿš€ Initializing DatabaseManager...'); if (!options.cerebrasApiKey) { console.warn('โš ๏ธ Cerebras API key not provided. Schema analysis will be limited.'); } this.cerebras = new cerebras_cloud_sdk_1.default({ apiKey: options.cerebrasApiKey, }); // Store database URL if provided if (options.databaseUrl) { this.databaseUrl = options.databaseUrl; console.log('โœ… Database URL configured'); } // Set model with default this.model = options.model || "llama-3.3-70b"; console.log(`โœ… Model configured: ${this.model}`); console.log('โœ… DatabaseManager initialized successfully'); } // Enhanced timeout wrapper with better error messages async withTimeout(promise, ms, errorMsg) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(errorMsg || `Operation timed out after ${ms} ms`)); }, ms); promise .then((value) => { clearTimeout(timer); resolve(value); }) .catch((err) => { clearTimeout(timer); reject(err); }); }); } // Enhanced database details detection with comprehensive error handling getDatabaseDetails(url) { if (!url) { throw new Error('Database URL is required'); } console.log('๐Ÿ” Analyzing database URL...'); console.log('๐Ÿ“Š URL (masked):', url.replace(/:[^:@]*@/, ':***@')); try { const dbType = (0, index_1.getDatabaseTypeFromUrl)(url); if (!dbType) { console.error('โŒ Could not determine database type from URL'); throw new Error(`Unsupported database type. Unable to determine database type from URL: ${url.substring(0, 50)}...`); } console.log(`โœ… Detected database type: ${dbType}`); try { const urlObj = new URL(url); console.log(`โœ… URL parsed successfully - Protocol: ${urlObj.protocol}, Host: ${urlObj.hostname}`); return { type: dbType, protocol: urlObj.protocol, connectionString: url }; } catch (urlError) { // For file paths (SQLite) console.log('๐Ÿ“ URL parsing failed, assuming file path for SQLite'); if (dbType === 'sqlite') { return { type: dbType, protocol: "file:", connectionString: url }; } console.error('โŒ Invalid URL format and not SQLite:', urlError); throw new Error(`Invalid URL format: ${url.substring(0, 50)}...`); } } catch (error) { console.error('โŒ Error in getDatabaseDetails:', error); throw error; } } // ----- Enhanced Logging Methods ----- formatLogMessage(level, context, message) { const timestamp = new Date().toISOString(); return JSON.stringify({ timestamp, level, method: context.method, action: context.action, message, duration: context.duration, ...context.metadata }); } log(level, context, message) { const formattedMessage = this.formatLogMessage(level, context, message); switch (level) { case index_1.LogLevel.ERROR: console.error(formattedMessage); break; case index_1.LogLevel.WARN: console.warn(formattedMessage); break; case index_1.LogLevel.DEBUG: console.debug(formattedMessage); break; default: console.log(formattedMessage); } } async logError(method, error, context) { const errorObject = error instanceof Error ? error : new Error(String(error)); console.error(`โŒ Error in ${method}:`, errorObject.message); this.log(index_1.LogLevel.ERROR, { method, action: 'error_handling', metadata: { errorName: errorObject.name, errorMessage: errorObject.message, errorStack: errorObject.stack, ...context } }, 'An error occurred'); } logInfo(method, message, data) { console.log(`โ„น๏ธ ${method}: ${message}`); this.log(index_1.LogLevel.INFO, { method, metadata: data }, message); } logExecutionTime(methodName, start) { const duration = perf_hooks_1.performance.now() - start; console.log(`โฑ๏ธ ${methodName} completed in ${duration.toFixed(2)}ms`); this.log(index_1.LogLevel.DEBUG, { method: methodName, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, `Execution completed`); } // ----- End Enhanced Logging Methods ----- // Enhanced schema extraction with comprehensive error handling async getSchemaInfo(databaseUrl) { const start = perf_hooks_1.performance.now(); const method = 'getSchemaInfo'; console.log('๐Ÿ” Starting database schema extraction...'); const url = databaseUrl || this.databaseUrl; if (!url) { const error = new Error('Database URL is required - provide it as parameter or in constructor options'); this.logError(method, error); throw error; } try { const dbDetails = this.getDatabaseDetails(url); this.logInfo(method, `Starting schema extraction for database type: ${dbDetails.type}`); console.log('โณ Creating database handler...'); const handler = (0, index_1.createDatabaseHandler)(dbDetails.type, { loggerFn: (level, context, message) => this.log(level, context, message) }); console.log('โณ Extracting schema information...'); const schema = await this.withTimeout(handler.getSchemaInfo(url), 60000, // 60 seconds for schema extraction 'Schema extraction timed out after 60 seconds'); this.logInfo(method, `Schema extraction successful: ${schema.tables.length} tables found`); console.log(`๐ŸŽ‰ Schema extraction completed successfully: ${schema.tables.length} tables`); return schema; } catch (error) { console.error('โŒ Schema extraction failed:', error); this.logError(method, error, { operation: 'getSchemaInfo' }); // Enhance error message based on error type if (error instanceof Error) { let enhancedMessage = error.message; if (error.message.includes('timeout')) { enhancedMessage = 'Schema extraction timed out. The database might be slow to respond or contain many tables.'; } else if (error.message.includes('ENOTFOUND')) { enhancedMessage = 'Database host not found. Please check the hostname in your connection string.'; } else if (error.message.includes('ECONNREFUSED')) { enhancedMessage = 'Connection refused. Please check if the database server is running and accessible.'; } else if (error.message.includes('authentication')) { enhancedMessage = 'Authentication failed. Please check your username and password.'; } else if (error.message.includes('SSL') || error.message.includes('ssl')) { enhancedMessage = 'SSL connection error. The database might require SSL or have SSL configuration issues.'; } throw new Error(enhancedMessage); } throw error; } finally { this.logExecutionTime(method, start); } } // Enhanced connection test with comprehensive error handling async testConnection(databaseUrl) { const start = perf_hooks_1.performance.now(); const method = 'testConnection'; console.log('๐Ÿงช Testing database connection...'); const url = databaseUrl || this.databaseUrl; if (!url) { const error = new Error('Database URL is required - provide it as parameter or in constructor options'); this.logError(method, error); throw error; } try { const dbDetails = this.getDatabaseDetails(url); this.logInfo(method, `Testing connection for database type: ${dbDetails.type}`); console.log('โณ Creating database handler for connection test...'); const handler = (0, index_1.createDatabaseHandler)(dbDetails.type, { loggerFn: (level, context, message) => this.log(level, context, message) }); console.log('โณ Testing database connection...'); const result = await this.withTimeout(handler.testConnection(url), this.DEFAULT_TIMEOUT, 'Connection test timed out after 30 seconds'); if (result) { this.logInfo(method, 'Connection test completed successfully'); console.log('โœ… Database connection test successful'); } return result; } catch (error) { console.error('โŒ Connection test failed:', error); this.logError(method, error, { operation: 'testConnection' }); // Enhance error message for common connection issues if (error instanceof Error) { let enhancedMessage = error.message; if (error.message.includes('timeout')) { enhancedMessage = 'Connection test timed out. The database server might be unreachable or slow to respond.'; } else if (error.message.includes('ENOTFOUND')) { enhancedMessage = 'Database host not found. Please verify the hostname in your connection string.'; } else if (error.message.includes('ECONNREFUSED')) { enhancedMessage = 'Connection refused. Please check if the database server is running and the port is correct.'; } else if (error.message.includes('authentication failed')) { enhancedMessage = 'Authentication failed. Please verify your username and password.'; } else if (error.message.includes('database') && error.message.includes('does not exist')) { enhancedMessage = 'Database does not exist. Please check the database name in your connection string.'; } throw new Error(enhancedMessage); } throw error; } finally { this.logExecutionTime(method, start); } } // Enhanced schema validation with better error handling validateAndCleanSchema(originalSchema, analyzedContent) { // Method name used for logging context if needed console.log('๐Ÿ” Validating and cleaning schema from LLM analysis...'); if (!analyzedContent || typeof analyzedContent !== 'object') { console.warn('โš ๏ธ Invalid analyzed content structure, using fallback'); return this.createFallbackSchema(originalSchema); } const contentObj = analyzedContent; if (!Array.isArray(contentObj.tables)) { console.warn('โš ๏ธ No tables array in analyzed content, using fallback'); return this.createFallbackSchema(originalSchema); } console.log(`๐Ÿ“Š Processing ${contentObj.tables.length} analyzed tables...`); const validatedTables = []; for (const analyzedTable of contentObj.tables) { try { const tableName = analyzedTable.table_name; const originalTable = originalSchema.tables.find(t => t.name === tableName); if (!originalTable) { console.warn(`โš ๏ธ Analyzed table ${tableName} not found in original schema, skipping`); continue; } const validColumns = []; if (Array.isArray(analyzedTable.columns)) { for (const analyzedColumn of analyzedTable.columns) { const columnName = analyzedColumn.column_name; const originalColumn = originalTable.columns.find(c => c.name === columnName); if (originalColumn) { validColumns.push({ column_name: columnName || originalColumn.name, description: analyzedColumn.description || '', data_type: analyzedColumn.data_type || originalColumn.type, nullable: typeof analyzedColumn.nullable === 'boolean' ? analyzedColumn.nullable : originalColumn.nullable, }); } } } // If no columns were validated, use original columns with empty descriptions if (validColumns.length === 0) { console.warn(`โš ๏ธ No columns validated for table ${tableName}, using original structure`); validColumns.push(...originalTable.columns.map(c => ({ column_name: c.name, description: '', data_type: c.type, nullable: c.nullable }))); } validatedTables.push({ table_name: tableName, description: analyzedTable.description || '', columns: validColumns }); console.log(`โœ… Validated table: ${tableName} with ${validColumns.length} columns`); } catch (tableError) { console.error(`โŒ Error validating table:`, tableError); // Continue with other tables } } console.log(`๐ŸŽ‰ Schema validation completed: ${validatedTables.length} tables validated`); return { tables: validatedTables }; } // Helper method to create fallback schema when AI analysis fails createFallbackSchema(originalSchema) { console.log('๐Ÿ”„ Creating fallback schema structure...'); return { tables: originalSchema.tables.map(t => ({ table_name: t.name, description: "Description not available from AI analysis.", columns: t.columns.map(c => ({ column_name: c.name, description: "Description not available from AI analysis.", data_type: c.type, nullable: c.nullable })) })) }; } // Add this method to your DatabaseManager class for optimal AI accuracy with minimal data optimizeSchemaForAccuracy(schema) { console.log('๐ŸŽฏ Optimizing schema for maximum AI accuracy with minimal data...'); const optimizedSchema = { ...schema, tables: schema.tables.map(table => { // Select the BEST sample row (most representative data) let bestSampleRow = null; if (table.sampleData && table.sampleData.length > 0) { // Find row with most non-null values and varied data types bestSampleRow = table.sampleData.reduce((best, current) => { const currentNonNulls = Object.values(current).filter(v => v !== null && v !== undefined && v !== '').length; const bestNonNulls = Object.values(best).filter(v => v !== null && v !== undefined && v !== '').length; return currentNonNulls > bestNonNulls ? current : best; }); // Optimize the sample row for AI context const optimizedRow = {}; Object.entries(bestSampleRow).forEach(([key, value]) => { if (typeof value === 'string') { // Keep meaningful strings but truncate very long ones if (value.length > 50) { // Reduced from 100 to 50 // For very long strings, keep beginning and end with indication of content type const start = value.substring(0, 20); // Reduced from 40 to 20 const end = value.substring(value.length - 10); // Reduced from 20 to 10 optimizedRow[key] = `${start}...[${value.length} chars]...${end}`; } else { optimizedRow[key] = value; } } else if (value instanceof Date) { optimizedRow[key] = value.toISOString(); } else if (typeof value === 'object' && value !== null) { // For JSON objects, provide a structure hint optimizedRow[key] = `{object with ${Object.keys(value).length} keys: ${Object.keys(value).slice(0, 2).join(', ')}}`; // Reduced from 3 to 2 } else { optimizedRow[key] = value; } }); bestSampleRow = optimizedRow; } return { ...table, sampleData: bestSampleRow ? [bestSampleRow] : [], columns: table.columns.map(col => ({ name: col.name, type: col.type, nullable: col.nullable })) }; }) }; // Calculate token estimate and enforce limits const schemaString = JSON.stringify(optimizedSchema); const estimatedTokens = Math.ceil(schemaString.length / 4); console.log(`๐Ÿ“Š Optimized schema - Estimated tokens: ${estimatedTokens}`); // If still too large, further reduce data if (estimatedTokens > 4000) { console.log('โš ๏ธ Schema still large, applying emergency optimization...'); return this.emergencyOptimizeSchema(optimizedSchema); } return optimizedSchema; } // Emergency optimization for very large schemas emergencyOptimizeSchema(schema) { console.log('๐Ÿšจ Applying emergency schema optimization...'); return { ...schema, tables: schema.tables.slice(0, 10).map(table => ({ ...table, sampleData: [], // Remove all sample data in emergency mode columns: table.columns.slice(0, 15).map(col => ({ name: col.name, type: col.type.length > 30 ? col.type.substring(0, 30) + '...' : col.type, // Truncate long types nullable: col.nullable })) })) }; } // UPDATED: Enhanced AI prompt for balanced table descriptions // Enhanced AI prompt for better accuracy getEnhancedAIPrompt() { return `You are a senior database architect and data analyst. Analyze the provided database schema with sample data and provide detailed, accurate descriptions. CRITICAL INSTRUCTIONS: 1. Examine the sample data VALUES carefully - they reveal the actual purpose and content 2. Look at column names, data types, AND the sample values together 3. Consider relationships between tables (foreign keys, similar column names) 4. Provide specific, actionable descriptions based on what you observe For each table: - Describe its PRIMARY business purpose based on the data you see - Explain how it fits into the overall system architecture - Note any obvious relationships to other tables For each column: - Describe what SPECIFIC type of data it stores (don't just restate the data type) - Explain its business purpose based on sample values - Note if it's clearly an ID, foreign key, status field, timestamp, etc. - Mention any patterns you notice in the sample data EXAMPLE of good descriptions: - "user_id": "Foreign key referencing the users table, identifies which user owns this record" - "status": "Enumerated field tracking order status, sample shows 'pending' indicating workflow states" - "created_at": "Timestamp when the record was created, used for auditing and chronological ordering" Format as JSON only: { "tables": [ { "table_name": "string", "description": "Detailed business purpose based on observed data and column patterns", "columns": [ { "column_name": "string", "description": "Specific purpose and data content based on sample values and column name", "data_type": "string (keep original)", "nullable": boolean } ] } ] } Database Schema with Representative Sample Data:`; } // NEW public method to generate the overall summary async getOverallSchemaSummary(analyzedSchema) { const method = 'getOverallSchemaSummary'; this.logInfo(method, 'Generating overall schema summary...'); if (!analyzedSchema || !analyzedSchema.tables || analyzedSchema.tables.length === 0) { this.log(index_1.LogLevel.WARN, { method }, 'Cannot generate summary from empty schema.'); return "Schema data was not available to generate a summary."; } // Create a simplified version of the schema (just table names and descriptions) // to send to the AI. This saves tokens and cost. const simplifiedSchema = analyzedSchema.tables.map((table) => ({ table_name: table.table_name, description: table.description, })); // A new, specific prompt to ask the AI for a high-level summary const summaryPrompt = `You are a senior database architect. Based on the provided list of tables and their descriptions, write a concise, one-paragraph summary of the entire database. Describe its overall purpose (e.g., "e-commerce platform," "blogging system"), mention the total number of tables, and identify the most important core tables. Respond with only the summary text, not JSON.`; try { const completion = await this.cerebras.chat.completions.create({ messages: [ { role: "system", content: summaryPrompt }, { role: "user", content: JSON.stringify(simplifiedSchema, null, 2) } ], model: this.model, temperature: 0.2, max_tokens: 500, // A summary doesn't need to be long }); const summary = completion.choices[0]?.message?.content; if (!summary) { throw new Error('Empty summary response from AI'); } this.logInfo(method, 'Successfully generated overall schema summary.'); return summary.trim(); } catch (error) { this.logError(method, error); // If the AI fails, create a simple, non-AI summary as a fallback return `A database schema containing ${analyzedSchema.tables.length} tables, including: ${analyzedSchema.tables.slice(0, 3).map((t) => t.table_name).join(', ')}.`; } } // FIXED: Enhanced schema analysis with improved internal schema detection async analyzeAndCacheSchema(databaseUrl, databaseId, forceAnalysis = false) { const method = 'analyzeAndCacheSchema'; const start = perf_hooks_1.performance.now(); console.log('๐Ÿง  Starting AI-powered schema analysis...'); try { if (!databaseUrl || !databaseId) { const error = new Error('Missing required parameters: databaseUrl and databaseId are both required'); this.logError(method, error); throw error; } console.log(`๐Ÿ“Š Analyzing schema for database ID: ${databaseId}`); if (forceAnalysis) { console.log('๐Ÿ”„ Forcing new analysis (skipping cache)'); } console.log('โณ Step 1: Getting database schema...'); const schema = await this.getSchemaInfo(databaseUrl); if (!schema?.tables?.length) { console.warn('โš ๏ธ No tables found or invalid schema structure'); return { tables: [] }; } console.log(`โœ… Schema retrieved: ${schema.tables.length} tables found`); // FIXED: More lenient internal database detection console.log('โณ Step 2: Checking for internal/system schemas...'); // Only check for critical internal database patterns const isApplicationInternalDB = this.isApplicationInternalDatabase(databaseUrl, schema); if (isApplicationInternalDB) { console.warn('โš ๏ธ This appears to be your application\'s internal database. Proceeding with analysis but be cautious.'); // Log warning but don't throw error - let user decide // Uncomment the lines below if you want to prevent internal DB analysis completely // const error = new Error('Internal application database detected. Please provide a different external database URL.'); // console.error('โŒ Internal application database detected'); // this.logError(method, error); // throw error; } console.log('โœ… Database validation passed - proceeding with AI analysis'); // Skip AI analysis if API key is not set if (!this.cerebras || !this.cerebras.apiKey) { console.warn('โš ๏ธ Cerebras API key is not available. Returning basic schema without AI descriptions.'); const fallbackSchema = this.createFallbackSchema(schema); // Add a basic summary for fallback schema when API key is missing fallbackSchema.overallSummary = `A database schema containing ${schema.tables.length} tables. AI analysis was not available due to missing API key.`; return fallbackSchema; } console.log('โณ Step 3: Preparing AI analysis prompt...'); // Optimize schema for maximum accuracy const optimizedSchema = this.optimizeSchemaForAccuracy(schema); const enhancedPrompt = this.getEnhancedAIPrompt(); try { console.log('โณ Step 4: Calling Cerebras AI API with optimized data...'); const cerebrasPromise = this.cerebras.chat.completions.create({ messages: [ { role: "system", content: enhancedPrompt }, { role: "user", content: JSON.stringify(optimizedSchema, null, 2) } ], model: this.model, temperature: 0.1, // Low temperature for consistent, accurate results max_tokens: 3000, // Reduced to prevent oversized responses top_p: 0.9 // Slightly higher for more natural language }); const completion = await this.withTimeout(cerebrasPromise, 30000, // Reduced timeout to prevent hanging 'Cerebras AI API call timed out after 30 seconds'); console.log('โœ… Received response from Cerebras AI'); const content = completion.choices[0].message.content; if (!content) { throw new Error('Empty response from Cerebras API'); } console.log('โณ Step 5: Processing AI response...'); let parsedAnalyzedContent; try { parsedAnalyzedContent = (0, robust_json_parser_1.robustJsonParse)(content); console.log('โœ… Successfully parsed Cerebras AI response'); // Log quality of descriptions received if (parsedAnalyzedContent.tables) { const avgDescLength = parsedAnalyzedContent.tables.reduce((sum, table) => { return sum + (table.description?.length || 0); }, 0) / parsedAnalyzedContent.tables.length; console.log(`๐Ÿ“Š Average table description length: ${avgDescLength.toFixed(0)} characters`); } } catch (parseError) { console.error('โŒ Failed to parse Cerebras response:', parseError); console.log('๐Ÿ“ Raw response content (first 500 chars):', content.substring(0, 500)); const fallbackSchema = this.createFallbackSchema(schema); // Add a basic summary for fallback schema when parsing fails fallbackSchema.overallSummary = `A database schema containing ${schema.tables.length} tables. AI analysis failed due to response parsing errors.`; return fallbackSchema; } const validatedSchema = this.validateAndCleanSchema(schema, parsedAnalyzedContent); console.log('๐ŸŽ‰ AI schema analysis completed successfully with enhanced accuracy'); // Generate overall schema summary console.log('โณ Generating overall schema summary...'); const overallSummary = await this.getOverallSchemaSummary(validatedSchema); console.log('โœ… Overall schema summary generated'); // Add the summary to the schema object for easy access validatedSchema.overallSummary = overallSummary; return validatedSchema; } catch (apiError) { console.error('โŒ Cerebras AI API error:', apiError); // Check if it's still a token limit error if (apiError instanceof Error && apiError.message.includes('length')) { console.log('โš ๏ธ Still hitting token limits, will implement emergency fallback'); // You could implement batch processing here as backup } console.log('๐Ÿ”„ Falling back to basic schema structure'); const fallbackSchema = this.createFallbackSchema(schema); // Add a basic summary for fallback schema fallbackSchema.overallSummary = `A database schema containing ${schema.tables.length} tables. Detailed analysis was not available.`; return fallbackSchema; } } catch (error) { console.error('โŒ Schema analysis failed:', error); this.logError(method, error); throw error; } finally { this.logExecutionTime(method, start); } } // Enhanced disconnect method async disconnect() { console.log('๐Ÿ”Œ DatabaseManager disconnect called'); console.log('โ„น๏ธ Current implementation creates connections per operation - no persistent connections to close'); } // Helper method to get supported database types getSupportedDatabaseTypes() { return ['postgres', 'postgresql', 'mysql', 'mariadb', 'mongodb', 'sqlite']; } // Helper method to validate connection string format validateConnectionString(connectionString) { const errors = []; if (!connectionString) { errors.push('Connection string is required'); return { isValid: false, errors }; } if (connectionString.length < 10) { errors.push('Connection string appears too short'); } try { const dbType = (0, index_1.getDatabaseTypeFromUrl)(connectionString); if (!dbType) { errors.push('Unable to determine database type from connection string'); return { isValid: false, errors }; } // Basic URL validation for non-file databases if (dbType !== 'sqlite') { try { new URL(connectionString); } catch { errors.push('Invalid URL format for database connection string'); } } return { isValid: errors.length === 0, errors, type: dbType }; } catch { errors.push('Failed to validate connection string format'); return { isValid: false, errors }; } } // Method to automatically detect and return database type from URL getDatabaseUrlType(databaseUrl) { const url = databaseUrl || this.databaseUrl; if (!url) { console.warn('โš ๏ธ Database URL is required to detect type'); return null; } console.log('๐Ÿ” Auto-detecting database type from URL...'); const dbType = (0, index_1.getDatabaseTypeFromUrl)(url); if (dbType) { console.log(`โœ… Detected database type: ${dbType}`); } else { console.warn('โš ๏ธ Could not determine database type from URL'); } return dbType; } // FIXED: More lenient method to detect the application's internal database isApplicationInternalDatabase(databaseUrl, schema) { try { const url = new URL(databaseUrl); // Get your application's database configuration from environment variables const appDatabaseUrl = process.env.DATABASE_URL; // Primary check: Compare with actual app database URL if available if (appDatabaseUrl) { try { const appUrl = new URL(appDatabaseUrl); // Check for exact match of all critical components if (url.hostname === appUrl.hostname && url.port === appUrl.port && url.pathname === appUrl.pathname && url.username === appUrl.username) { console.log('๐Ÿšจ Detected potential match for application internal database via connection string comparison'); // Additional check: If passwords also match, it's definitely the same DB if (url.password === appUrl.password) { console.log('๐Ÿšจ Password match confirmed - this is the internal database'); return true; } // If everything else matches but password is different, it might be a different user/role console.log('โš ๏ธ Connection details match but password differs - treating as external database'); return false; } } catch (error) { console.warn('Could not parse application database URL for comparison:', error); } } // Secondary check: Look for exact table structure match const requiredInternalTables = ['db_connections', 'users', 'chats', 'messages', 'query_executions']; const schemaTableNames = schema.tables.map(t => t.name.toLowerCase()); const hasAllInternalTables = requiredInternalTables.every(tableName => schemaTableNames.includes(tableName)); // Only flag as internal if: // 1. ALL required tables exist // 2. Total table count is exactly the same (no extra tables) // 3. It's on localhost OR we have DATABASE_URL set for comparison if (hasAllInternalTables && schema.tables.length === requiredInternalTables.length && (url.hostname === 'localhost' || url.hostname === '127.0.0.1' || appDatabaseUrl)) { console.log('๐Ÿšจ Detected internal database: Schema matches exactly and host/config criteria met'); return true; } // If it has some internal tables but also has other tables, it's probably not our internal DB if (hasAllInternalTables && schema.tables.length > requiredInternalTables.length) { console.log('โ„น๏ธ Has internal tables but also has additional tables - treating as external database'); return false; } console.log('โ„น๏ธ No internal database match found based on current criteria'); return false; } catch (error) { console.warn('Could not parse database URL for internal detection:', error); return false; } } } exports.default = DatabaseManager; exports.DatabaseManager = DatabaseManager; //# sourceMappingURL=DatabaseManager.js.map