diff.ts61 lines · main
| 1 | import type { SchemaDef } from './schema.js'; |
| 2 | import type { TableDef } from './table.js'; |
| 3 | |
| 4 | export type Change = |
| 5 | | { kind: 'create_table'; table: string; def: TableDef } |
| 6 | | { kind: 'drop_table'; table: string } |
| 7 | | { kind: 'add_column'; table: string; column: string; def: TableDef['columns'][string] } |
| 8 | | { kind: 'drop_column'; table: string; column: string }; |
| 9 | |
| 10 | export interface DiffResult { |
| 11 | readonly changes: readonly Change[]; |
| 12 | readonly destructive: boolean; |
| 13 | } |
| 14 | |
| 15 | /** |
| 16 | * Compute the set of changes to go from `prev` to `next`. |
| 17 | * |
| 18 | * Phase 1 scope is deliberately narrow: add/drop tables and add/drop |
| 19 | * columns. It does not yet detect column-type changes, index changes, |
| 20 | * or default-value updates — those land alongside the migration runner |
| 21 | * in Phase 2 per BUILD_PLAN.md. |
| 22 | */ |
| 23 | export function diff(prev: SchemaDef | null, next: SchemaDef): DiffResult { |
| 24 | const changes: Change[] = []; |
| 25 | const prevTables = prev?.tables ?? {}; |
| 26 | |
| 27 | for (const [name, def] of Object.entries(next.tables)) { |
| 28 | const existing = prevTables[name]; |
| 29 | if (!existing) { |
| 30 | changes.push({ kind: 'create_table', table: name, def }); |
| 31 | continue; |
| 32 | } |
| 33 | for (const [colName, colDef] of Object.entries(def.columns)) { |
| 34 | if (!(colName in existing.columns)) { |
| 35 | changes.push({ kind: 'add_column', table: name, column: colName, def: colDef }); |
| 36 | } |
| 37 | } |
| 38 | for (const colName of Object.keys(existing.columns)) { |
| 39 | if (!(colName in def.columns)) { |
| 40 | changes.push({ kind: 'drop_column', table: name, column: colName }); |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | for (const name of Object.keys(prevTables)) { |
| 46 | if (!(name in next.tables)) { |
| 47 | changes.push({ kind: 'drop_table', table: name }); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | const destructive = changes.some((c) => c.kind === 'drop_table' || c.kind === 'drop_column'); |
| 52 | return { changes, destructive }; |
| 53 | } |
| 54 | |
| 55 | export function summariseDiff(result: DiffResult): Record<string, number> { |
| 56 | const counts: Record<string, number> = {}; |
| 57 | for (const c of result.changes) { |
| 58 | counts[c.kind] = (counts[c.kind] ?? 0) + 1; |
| 59 | } |
| 60 | return counts; |
| 61 | } |