@bhagat-surya-dev/dashchat-database-manager
Version:
AI-powered database schema analysis and management library
211 lines • 8.85 kB
JavaScript
;
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