import { Client } from 'pg'; import * as fs from 'fs'; import * as path from 'path'; interface Column { name: string; type: string; nullable: boolean; isPrimaryKey: boolean; isForeignKey: boolean; } interface Relation { columnName: string; referencedTable: string; referencedColumn: string; } interface Table { name: string; schema: string; columns: Column[]; relations: Relation[]; } interface DatabaseSchema { database: string; exportedAt: string; tables: Table[]; } async function fetchTables(client: Client): Promise { const query = ` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name; `; const result = await client.query(query); return result.rows.map((row) => row.table_name); } async function fetchColumns( client: Client, tableName: string, ): Promise { const query = ` SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary_key, CASE WHEN fk.column_name IS NOT NULL THEN true ELSE false END as is_foreign_key FROM information_schema.columns c LEFT JOIN ( SELECT ku.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage ku ON tc.constraint_name = ku.constraint_name AND tc.table_schema = ku.table_schema WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = $1 AND tc.table_schema = 'public' ) pk ON c.column_name = pk.column_name LEFT JOIN ( SELECT ku.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage ku ON tc.constraint_name = ku.constraint_name AND tc.table_schema = ku.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public' ) fk ON c.column_name = fk.column_name WHERE c.table_name = $1 AND c.table_schema = 'public' ORDER BY c.ordinal_position; `; const result = await client.query(query, [tableName]); return result.rows.map((row) => ({ name: row.column_name, type: row.data_type, nullable: row.is_nullable === 'YES', isPrimaryKey: row.is_primary_key, isForeignKey: row.is_foreign_key, })); } async function fetchRelations( client: Client, tableName: string, ): Promise { const query = ` SELECT tc.constraint_name, kcu.column_name, ccu.table_name AS referenced_table, ccu.column_name AS referenced_column, rc.delete_rule AS on_delete, rc.update_rule AS on_update FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.referential_constraints rc ON tc.constraint_name = rc.constraint_name AND tc.table_schema = rc.constraint_schema JOIN information_schema.key_column_usage ccu ON ccu.constraint_name = rc.unique_constraint_name AND ccu.table_schema = rc.unique_constraint_schema AND ccu.ordinal_position = kcu.position_in_unique_constraint WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public' ORDER BY tc.constraint_name, kcu.ordinal_position; `; const result = await client.query(query, [tableName]); return result.rows.map((row) => ({ columnName: row.column_name, referencedTable: row.referenced_table, referencedColumn: row.referenced_column, })); } export async function exportDatabaseSchema( dbName: string, dbUser: string, dbHost: string = 'localhost', dbPort: number = 5432, outputDir?: string, ): Promise<{ success: boolean; outputPath?: string; error?: string }> { const client = new Client({ host: dbHost, port: dbPort, database: dbName, user: dbUser, }); try { await client.connect(); const tables = await fetchTables(client); const schema: DatabaseSchema = { database: dbName, exportedAt: new Date().toISOString(), tables: [], }; for (const tableName of tables) { const columns = await fetchColumns(client, tableName); const relations = await fetchRelations(client, tableName); schema.tables.push({ name: tableName, schema: 'public', columns, relations, }); } const outputPath = path.join( outputDir || process.cwd(), `${dbName}_schema.json`, ); fs.writeFileSync(outputPath, JSON.stringify(schema, null, 2)); await client.end(); return { success: true, outputPath, }; } catch (error) { await client.end(); return { success: false, error: error instanceof Error ? error.message : String(error), }; } }