@bhagat-surya-dev/dashchat-database-manager
Version:
AI-powered database schema analysis and management library
775 lines (769 loc) โข 39.5 kB
JavaScript
;
/**
* 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