import _ from "lodash"; import type { BUN_MARIADB_DatabaseSchemaType, BUN_MARIADB_TableSchemaType, CreateDBSchemaParams, } from "../../types"; /** * Topologically order tables so referenced (parent) tables come before the * tables that reference them. Declaration order is kept wherever the FK * graph allows it. * * - Self references (e.g. `comments.parent_comment_id`) are ignored. * - References to tables outside the schema are ignored. * - Cycles do not throw: a table already on the current path is skipped, * so every table is still emitted exactly once. Foreign keys are created * after all tables exist, so cycles are safe to sync. */ export default async function orderDBSchema({ db_schema, }: CreateDBSchemaParams): Promise { const new_db_schema = _.cloneDeep(db_schema); const tables = new_db_schema.tables; const tables_by_name = new Map(); for (const table of tables) { if (table) tables_by_name.set(table.tableName, table); } const ordered: BUN_MARIADB_TableSchemaType[] = []; const visited = new Set(); const visiting = new Set(); function visit(table: BUN_MARIADB_TableSchemaType) { if (visited.has(table.tableName) || visiting.has(table.tableName)) { return; } visiting.add(table.tableName); for (const field of table.fields || []) { const dst_table_name = field?.foreignKey?.destinationTableName; if (!dst_table_name || dst_table_name === table.tableName) { continue; } const fk_table = tables_by_name.get(dst_table_name); if (fk_table) { visit(fk_table); } } visiting.delete(table.tableName); visited.add(table.tableName); ordered.push(table); } for (const table of tables) { if (table) visit(table); } new_db_schema.tables = ordered; return new_db_schema; }