UNPKG

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

Version:

AI-powered database schema analysis and management library

211 lines 8.85 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.supabaseHandler = exports.SupabaseHandler = void 0; // Supabase specific handler that extends PostgreSQL capabilities const perf_hooks_1 = require("perf_hooks"); const postgres_1 = __importDefault(require("postgres")); const base_handler_1 = require("./base-handler"); /** * A specialized handler for Supabase PostgreSQL databases * This class handles the specific connection requirements and URL formats used by Supabase */ class SupabaseHandler extends base_handler_1.BaseDatabaseHandler { getDatabaseType() { return 'postgres'; } /** * Check if a URL is a Supabase URL based on hostname patterns * @param databaseUrl The database URL to check * @returns boolean indicating if the URL is a Supabase URL */ static isSupabaseUrl(databaseUrl) { try { const url = new URL(databaseUrl); return (url.hostname.includes('supabase.co') || url.hostname.includes('supabase.com') || url.hostname.includes('pooler.supabase.com') || (url.hostname.includes('postgres.') && url.port === '6543')); } catch { return false; } } /** * Fix common issues with Supabase URL formats * @param databaseUrl The database URL to fix * @returns The corrected database URL */ static fixSupabaseUrl(databaseUrl) { try { const url = new URL(databaseUrl); // Replace aws regions with standard supabase.co format if needed if (url.hostname.includes('aws-') && url.hostname.includes('supabase.com')) { // Extract project ID from the username part const usernameMatch = url.username.match(/postgres\.([a-zA-Z0-9]+)/); if (usernameMatch && usernameMatch[1]) { const projectId = usernameMatch[1]; url.hostname = `${projectId}.supabase.co`; url.port = '5432'; // Standard PostgreSQL port for direct connection } } return url.toString(); } catch { // If URL parsing fails, return original return databaseUrl; } } /** * Test if connection to Supabase is valid * Uses postgres.js for better compatibility with Supabase */ async testConnectionWithDetails(databaseUrl) { const method = 'testConnectionWithDetails'; const start = perf_hooks_1.performance.now(); if (!databaseUrl) { this.logError(method, new Error('Database URL is required')); return { success: false, error: 'Database URL is required' }; } // Fix any common URL issues const fixedUrl = SupabaseHandler.fixSupabaseUrl(databaseUrl); this.logInfo(method, 'Using corrected Supabase URL', { databaseUrl: fixedUrl.replace(/:[^:@]*@/, ':***@') // Hide password in logs }); try { // Configure postgres.js client with appropriate SSL settings const sql = (0, postgres_1.default)(fixedUrl, { ssl: 'require', connect_timeout: 30, idle_timeout: 30, max_lifetime: 60 * 60, max: 1, // Use minimal connections for test }); // Test with a simple query await sql `SELECT 1 as test`; // Clean up connection await sql.end(); this.logInfo(method, 'Successfully connected to Supabase database'); return { success: true }; } catch (error) { this.logError(method, error, { databaseUrl: fixedUrl.replace(/:[^:@]*@/, ':***@') }); // Enhanced error messages for common Supabase issues let errorMessage = 'Unknown connection error'; if (error instanceof Error) { errorMessage = error.message; if (error.message.includes('connect ETIMEDOUT')) { errorMessage = 'Connection timed out. Your IP might not be in the Supabase allowlist.'; } else if (error.message.includes('password authentication failed')) { errorMessage = 'Authentication failed. Check your password or database user.'; } } return { success: false, error: errorMessage }; } finally { const duration = perf_hooks_1.performance.now() - start; this.log(base_handler_1.LogLevel.DEBUG, { method, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, 'Connection test completed'); } } // Implement the base interface async testConnection(databaseUrl) { const result = await this.testConnectionWithDetails(databaseUrl); return result.success; } /** * Get schema information from Supabase database * Uses postgres.js for better compatibility with Supabase */ async getSchemaInfo(databaseUrl) { const method = 'getSchemaInfo'; const start = perf_hooks_1.performance.now(); if (!databaseUrl) { this.log(base_handler_1.LogLevel.ERROR, { method, action: 'validation' }, 'Database URL is required.'); throw new Error('Please provide a valid database URL'); } // Fix any common URL issues const fixedUrl = SupabaseHandler.fixSupabaseUrl(databaseUrl); this.logInfo(method, 'Starting schema extraction for Supabase', { databaseUrl: fixedUrl.replace(/:[^:@]*@/, ':***@') // Hide password in logs }); try { // Configure postgres.js client with appropriate SSL settings const sql = (0, postgres_1.default)(fixedUrl, { ssl: 'require', connect_timeout: 30, idle_timeout: 30, max_lifetime: 60 * 60, }); // Get all tables from the public schema const tables = await sql ` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name; `; if (!tables || tables.length === 0) { this.logInfo(method, 'No tables found in Supabase database'); return { databaseType: this.getDatabaseType(), tables: [] }; } this.logInfo(method, `Found ${tables.length} tables in Supabase database`); // Get schema information for each table const tablesInfo = await Promise.all(tables.map(async (table) => { const tableName = table.table_name; // Get column information const columns = await sql ` SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = ${tableName} AND table_schema = 'public' ORDER BY ordinal_position; `; // Get sample data const sampleData = await sql ` SELECT * FROM ${sql(tableName)} LIMIT ${this.maxSampleSize}; `; return { name: tableName, columns: columns.map(col => ({ name: col.column_name, type: col.data_type, nullable: col.is_nullable === 'YES' })), sampleData: sampleData }; })); // Clean up connection await sql.end(); return { databaseType: this.getDatabaseType(), tables: tablesInfo }; } catch (error) { this.logError(method, error, { databaseUrl: fixedUrl.replace(/:[^:@]*@/, ':***@') }); throw error; } finally { const duration = perf_hooks_1.performance.now() - start; this.log(base_handler_1.LogLevel.DEBUG, { method, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, 'Schema extraction completed'); } } } exports.SupabaseHandler = SupabaseHandler; // Singleton instance for use throughout the application exports.supabaseHandler = new SupabaseHandler(); //# sourceMappingURL=supabase-handler.js.map