bun-query-builder 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -17449,12 +17449,12 @@ async function readColumns(qb, dialect, table) {
17449
17449
  }
17450
17450
  if (dialect === "postgres") {
17451
17451
  const rows2 = await qb.unsafe(`SELECT c.column_name, c.data_type, c.is_nullable,
17452
- (SELECT COUNT(*) FROM information_schema.table_constraints tc
17452
+ (SELECT COUNT(*) FROM information_schema.table_constraints tc
17453
17453
  JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
17454
17454
  WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
17455
- FROM information_schema.columns c
17456
- WHERE c.table_name = $1 AND c.table_schema = 'public'
17457
- ORDER BY c.ordinal_position`, [table]);
17455
+ FROM information_schema.columns c
17456
+ WHERE c.table_name = $1 AND c.table_schema = 'public'
17457
+ ORDER BY c.ordinal_position`, [table]);
17458
17458
  return rows2.map((r) => ({
17459
17459
  name: r.column_name,
17460
17460
  sqlType: String(r.data_type || ""),
@@ -17463,9 +17463,9 @@ async function readColumns(qb, dialect, table) {
17463
17463
  }));
17464
17464
  }
17465
17465
  const rows = await qb.unsafe(`SELECT column_name, data_type, is_nullable, column_key
17466
- FROM information_schema.columns
17467
- WHERE table_name = ? AND table_schema = DATABASE()
17468
- ORDER BY ordinal_position`, [table]);
17466
+ FROM information_schema.columns
17467
+ WHERE table_name = ? AND table_schema = DATABASE()
17468
+ ORDER BY ordinal_position`, [table]);
17469
17469
  return rows.map((r) => ({
17470
17470
  name: r.column_name ?? r.COLUMN_NAME,
17471
17471
  sqlType: String(r.data_type ?? r.DATA_TYPE ?? ""),
@@ -17512,6 +17512,281 @@ async function introspectDatabase(opts = {}) {
17512
17512
  }
17513
17513
  return out;
17514
17514
  }
17515
+ function sqlTypeToNormalized(rawType, dialect, ctx) {
17516
+ if (ctx?.enumValues && ctx.enumValues.length > 0)
17517
+ return "enum";
17518
+ const t = String(rawType || "").toLowerCase().trim();
17519
+ const base = t.replace(/\(.*$/, "").replace(/\s+/g, " ").trim();
17520
+ if (dialect === "mysql" && /^tinyint\(1\)/.test(t))
17521
+ return "boolean";
17522
+ if (/^bool/.test(base))
17523
+ return "boolean";
17524
+ if (base === "int8" || base === "bigint" || base === "bigserial")
17525
+ return "bigint";
17526
+ if (/^(?:int|integer|smallint|mediumint|serial|int2|int4|tinyint)/.test(base))
17527
+ return "integer";
17528
+ if (/^(?:varchar|character varying|nvarchar|nchar|char|character|bpchar)/.test(base)) {
17529
+ const m = t.match(/\((\d+)\)/);
17530
+ const len = m ? Number(m[1]) : undefined;
17531
+ return len !== undefined && len > 255 ? "text" : "string";
17532
+ }
17533
+ if (/text|clob/.test(base))
17534
+ return "text";
17535
+ if (base === "float8" || /^double/.test(base))
17536
+ return "double";
17537
+ if (/^(?:real|float4|float)/.test(base))
17538
+ return "float";
17539
+ if (/^(?:numeric|decimal|money)/.test(base))
17540
+ return "decimal";
17541
+ if (base === "date")
17542
+ return "date";
17543
+ if (/^(?:timestamp|datetime)/.test(base))
17544
+ return "datetime";
17545
+ if (/^time/.test(base))
17546
+ return "string";
17547
+ if (/json/.test(base))
17548
+ return "json";
17549
+ if (/^enum/.test(base))
17550
+ return "enum";
17551
+ return "string";
17552
+ }
17553
+ function stripIndexPrefix(physicalName, table) {
17554
+ const prefix = `${table}_`;
17555
+ return physicalName.startsWith(prefix) ? physicalName.slice(prefix.length) : physicalName;
17556
+ }
17557
+ function asAction(raw2) {
17558
+ const v = String(raw2 ?? "").toLowerCase().trim();
17559
+ if (v === "cascade" || v === "restrict" || v === "set null")
17560
+ return v;
17561
+ return;
17562
+ }
17563
+ async function buildSqliteTable(qb, table) {
17564
+ const colRows = await qb.unsafe(`PRAGMA table_info(${JSON.stringify(table)})`);
17565
+ const idxList = await qb.unsafe(`PRAGMA index_list(${JSON.stringify(table)})`);
17566
+ const fkList = await qb.unsafe(`PRAGMA foreign_key_list(${JSON.stringify(table)})`);
17567
+ const ddlRows = await qb.unsafe(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`, [table]);
17568
+ const ddl = String(ddlRows?.[0]?.sql ?? "");
17569
+ const fkByColumn = new Map;
17570
+ for (const fk of fkList) {
17571
+ fkByColumn.set(String(fk.from), {
17572
+ table: String(fk.table),
17573
+ column: String(fk.to ?? "id"),
17574
+ onDelete: asAction(fk.on_delete),
17575
+ onUpdate: asAction(fk.on_update)
17576
+ });
17577
+ }
17578
+ const indexes = [];
17579
+ const uniqueColumns = new Set;
17580
+ for (const idx of idxList) {
17581
+ const origin = String(idx.origin ?? "");
17582
+ const physName = String(idx.name ?? "");
17583
+ if (origin === "pk" || physName.startsWith("sqlite_autoindex_"))
17584
+ continue;
17585
+ const info = await qb.unsafe(`PRAGMA index_info(${JSON.stringify(physName)})`);
17586
+ const columns2 = info.map((r) => String(r.name)).filter(Boolean);
17587
+ if (columns2.length === 0)
17588
+ continue;
17589
+ const isUnique = Number(idx.unique) === 1;
17590
+ if (isUnique && columns2.length === 1)
17591
+ uniqueColumns.add(columns2[0]);
17592
+ indexes.push({ name: stripIndexPrefix(physName, table), columns: columns2, type: isUnique ? "unique" : "index" });
17593
+ }
17594
+ const columns = colRows.map((r) => {
17595
+ const name = String(r.name);
17596
+ const enumValues = parseSqliteCheckEnum(ddl, name);
17597
+ const isPrimaryKey = Number(r.pk) > 0;
17598
+ const dflt = r.dflt_value;
17599
+ const hasDefault = dflt !== null && dflt !== undefined;
17600
+ return {
17601
+ name,
17602
+ type: sqlTypeToNormalized(String(r.type ?? ""), "sqlite", { enumValues }),
17603
+ isPrimaryKey,
17604
+ isUnique: uniqueColumns.has(name),
17605
+ isNullable: isPrimaryKey ? false : Number(r.notnull) === 0,
17606
+ hasDefault,
17607
+ defaultValue: hasDefault ? normalizeRawDefault(dflt) : undefined,
17608
+ enumValues,
17609
+ references: fkByColumn.get(name)
17610
+ };
17611
+ });
17612
+ return { table, columns, indexes };
17613
+ }
17614
+ function parseSqliteCheckEnum(ddl, column) {
17615
+ if (!ddl)
17616
+ return;
17617
+ const re = new RegExp(`(?:"${column}"|\`${column}\`|\\b${column}\\b)\\s+IN\\s*\\(([^)]*)\\)`, "i");
17618
+ const m = ddl.match(re);
17619
+ if (!m)
17620
+ return;
17621
+ const values = m[1].split(",").map((s) => s.trim().replace(/^'(.*)'$/s, "$1").replace(/''/g, "'")).filter((s) => s.length > 0);
17622
+ return values.length > 0 ? values : undefined;
17623
+ }
17624
+ function normalizeRawDefault(raw2) {
17625
+ if (typeof raw2 === "number" || typeof raw2 === "boolean")
17626
+ return raw2;
17627
+ return String(raw2);
17628
+ }
17629
+ async function buildPgTable(qb, table) {
17630
+ const colRows = await qb.unsafe(`SELECT c.column_name, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length,
17631
+ (SELECT COUNT(*) FROM information_schema.table_constraints tc
17632
+ JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
17633
+ WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
17634
+ FROM information_schema.columns c
17635
+ WHERE c.table_name = $1 AND c.table_schema = 'public'
17636
+ ORDER BY c.ordinal_position`, [table]);
17637
+ const idxRows = await qb.unsafe(`SELECT i.relname AS index_name, ix.indisunique AS is_unique, ix.indisprimary AS is_primary,
17638
+ a.attname AS column_name, array_position(ix.indkey, a.attnum) AS ord,
17639
+ pg_get_expr(ix.indpred, ix.indrelid) AS where_clause
17640
+ FROM pg_class t
17641
+ JOIN pg_index ix ON t.oid = ix.indrelid
17642
+ JOIN pg_class i ON i.oid = ix.indexrelid
17643
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
17644
+ WHERE t.relname = $1 AND t.relkind = 'r'
17645
+ ORDER BY i.relname, ord`, [table]);
17646
+ const fkRows = await qb.unsafe(`SELECT kcu.column_name, ccu.table_name AS ref_table, ccu.column_name AS ref_column,
17647
+ rc.delete_rule, rc.update_rule
17648
+ FROM information_schema.table_constraints tc
17649
+ JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name
17650
+ JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name
17651
+ JOIN information_schema.referential_constraints rc ON rc.constraint_name = tc.constraint_name
17652
+ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public'`, [table]);
17653
+ const fkByColumn = new Map;
17654
+ for (const fk of fkRows) {
17655
+ fkByColumn.set(String(fk.column_name), {
17656
+ table: String(fk.ref_table),
17657
+ column: String(fk.ref_column ?? "id"),
17658
+ onDelete: asAction(fk.delete_rule),
17659
+ onUpdate: asAction(fk.update_rule)
17660
+ });
17661
+ }
17662
+ const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
17663
+ indexName: String(r.index_name),
17664
+ isUnique: r.is_unique === true || r.is_unique === "t",
17665
+ isPrimary: r.is_primary === true || r.is_primary === "t",
17666
+ column: String(r.column_name),
17667
+ where: r.where_clause ? String(r.where_clause) : undefined
17668
+ })));
17669
+ const columns = colRows.map((r) => {
17670
+ const name = String(r.column_name);
17671
+ const isPrimaryKey = Number(r.is_pk) > 0;
17672
+ const rawDefault = r.column_default == null ? null : String(r.column_default);
17673
+ const isSerial = rawDefault != null && /nextval\(/i.test(rawDefault);
17674
+ const hasDefault = rawDefault != null && !isSerial;
17675
+ return {
17676
+ name,
17677
+ type: sqlTypeToNormalized(String(r.data_type ?? r.udt_name ?? ""), "postgres"),
17678
+ isPrimaryKey,
17679
+ isUnique: uniqueColumns.has(name),
17680
+ isNullable: isPrimaryKey ? false : String(r.is_nullable).toUpperCase() === "YES",
17681
+ hasDefault,
17682
+ defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
17683
+ references: fkByColumn.get(name)
17684
+ };
17685
+ });
17686
+ return { table, columns, indexes };
17687
+ }
17688
+ async function buildMysqlTable(qb, table) {
17689
+ const colRows = await qb.unsafe(`SELECT column_name, data_type, column_type, is_nullable, column_default, column_key
17690
+ FROM information_schema.columns
17691
+ WHERE table_name = ? AND table_schema = DATABASE()
17692
+ ORDER BY ordinal_position`, [table]);
17693
+ const idxRows = await qb.unsafe(`SELECT index_name, non_unique, seq_in_index, column_name
17694
+ FROM information_schema.statistics
17695
+ WHERE table_name = ? AND table_schema = DATABASE()
17696
+ ORDER BY index_name, seq_in_index`, [table]);
17697
+ const fkRows = await qb.unsafe(`SELECT kcu.column_name, kcu.referenced_table_name AS ref_table, kcu.referenced_column_name AS ref_column,
17698
+ rc.delete_rule, rc.update_rule
17699
+ FROM information_schema.key_column_usage kcu
17700
+ JOIN information_schema.referential_constraints rc
17701
+ ON rc.constraint_name = kcu.constraint_name AND rc.constraint_schema = kcu.table_schema
17702
+ WHERE kcu.table_name = ? AND kcu.table_schema = DATABASE() AND kcu.referenced_table_name IS NOT NULL`, [table]);
17703
+ const fkByColumn = new Map;
17704
+ for (const fk of fkRows) {
17705
+ fkByColumn.set(String(fk.column_name ?? fk.COLUMN_NAME), {
17706
+ table: String(fk.ref_table ?? fk.REF_TABLE),
17707
+ column: String(fk.ref_column ?? fk.REF_COLUMN ?? "id"),
17708
+ onDelete: asAction(fk.delete_rule ?? fk.DELETE_RULE),
17709
+ onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE)
17710
+ });
17711
+ }
17712
+ const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
17713
+ indexName: String(r.index_name ?? r.INDEX_NAME),
17714
+ isUnique: Number(r.non_unique ?? r.NON_UNIQUE) === 0,
17715
+ isPrimary: String(r.index_name ?? r.INDEX_NAME).toUpperCase() === "PRIMARY",
17716
+ column: String(r.column_name ?? r.COLUMN_NAME)
17717
+ })));
17718
+ const columns = colRows.map((r) => {
17719
+ const name = String(r.column_name ?? r.COLUMN_NAME);
17720
+ const columnType = String(r.column_type ?? r.COLUMN_TYPE ?? "");
17721
+ const enumValues = parseMysqlEnum(columnType);
17722
+ const key = String(r.column_key ?? r.COLUMN_KEY ?? "").toUpperCase();
17723
+ const isPrimaryKey = key === "PRI";
17724
+ const rawDefault = r.column_default ?? r.COLUMN_DEFAULT;
17725
+ const hasDefault = rawDefault != null;
17726
+ return {
17727
+ name,
17728
+ type: sqlTypeToNormalized(String(r.data_type ?? r.DATA_TYPE ?? columnType), "mysql", { enumValues }),
17729
+ isPrimaryKey,
17730
+ isUnique: uniqueColumns.has(name),
17731
+ isNullable: isPrimaryKey ? false : String(r.is_nullable ?? r.IS_NULLABLE).toUpperCase() === "YES",
17732
+ hasDefault,
17733
+ defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
17734
+ enumValues,
17735
+ references: fkByColumn.get(name)
17736
+ };
17737
+ });
17738
+ return { table, columns, indexes };
17739
+ }
17740
+ function parseMysqlEnum(columnType) {
17741
+ const m = columnType.match(/^enum\((.*)\)$/i);
17742
+ if (!m)
17743
+ return;
17744
+ return m[1].split(",").map((s) => s.trim().replace(/^'(.*)'$/s, "$1").replace(/''/g, "'")).filter((s) => s.length > 0);
17745
+ }
17746
+ function groupPhysicalIndexes(table, rows) {
17747
+ const byName = new Map;
17748
+ for (const r of rows) {
17749
+ if (r.isPrimary)
17750
+ continue;
17751
+ const entry = byName.get(r.indexName) ?? { isUnique: r.isUnique, isPrimary: r.isPrimary, columns: [], where: r.where };
17752
+ entry.columns.push(r.column);
17753
+ byName.set(r.indexName, entry);
17754
+ }
17755
+ const indexes = [];
17756
+ const uniqueColumns = new Set;
17757
+ for (const [physName, entry] of byName) {
17758
+ if (entry.columns.length === 0)
17759
+ continue;
17760
+ if (entry.isUnique && entry.columns.length === 1)
17761
+ uniqueColumns.add(entry.columns[0]);
17762
+ indexes.push({
17763
+ name: stripIndexPrefix(physName, table),
17764
+ columns: entry.columns,
17765
+ type: entry.isUnique ? "unique" : "index",
17766
+ where: entry.where
17767
+ });
17768
+ }
17769
+ return { indexes, uniqueColumns };
17770
+ }
17771
+ async function buildPlanFromDatabase(dialect, opts = {}) {
17772
+ const d = dialect || config5.dialect || "postgres";
17773
+ const qb = createQueryBuilder();
17774
+ const allTables = opts.tables?.length ? opts.tables : await listTables(qb, d);
17775
+ const tables = allTables.filter((t) => t !== "migrations");
17776
+ const out = [];
17777
+ for (const table of tables) {
17778
+ let plan;
17779
+ if (d === "sqlite")
17780
+ plan = await buildSqliteTable(qb, table);
17781
+ else if (d === "postgres")
17782
+ plan = await buildPgTable(qb, table);
17783
+ else
17784
+ plan = await buildMysqlTable(qb, table);
17785
+ if (plan.columns.length > 0)
17786
+ out.push(plan);
17787
+ }
17788
+ return { dialect: d, tables: out };
17789
+ }
17515
17790
  var init_introspect_db = __esm(() => {
17516
17791
  init_config();
17517
17792
  init_src2();
@@ -17696,6 +17971,15 @@ class MySQLDriver {
17696
17971
  }
17697
17972
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} MODIFY COLUMN ${parts.join(" ")};`;
17698
17973
  }
17974
+ renameColumn(tableName, from, to) {
17975
+ return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
17976
+ }
17977
+ renameTable(from, to) {
17978
+ return `RENAME TABLE ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
17979
+ }
17980
+ rebuildTable() {
17981
+ throw new Error("[migrations] rebuildTable is only implemented for SQLite; MySQL uses in-place ALTER.");
17982
+ }
17699
17983
  dropTable(tableName) {
17700
17984
  return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;
17701
17985
  }
@@ -17858,6 +18142,15 @@ class PostgresDriver {
17858
18142
  const typeSql = this.getColumnType(column);
17859
18143
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} ALTER COLUMN ${this.quoteIdentifier(column.name)} TYPE ${typeSql} USING ${this.quoteIdentifier(column.name)}::${typeSql};`;
17860
18144
  }
18145
+ renameColumn(tableName, from, to) {
18146
+ return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
18147
+ }
18148
+ renameTable(from, to) {
18149
+ return `ALTER TABLE ${this.quoteIdentifier(from)} RENAME TO ${this.quoteIdentifier(to)};`;
18150
+ }
18151
+ rebuildTable() {
18152
+ throw new Error("[migrations] rebuildTable is only implemented for SQLite; Postgres uses in-place ALTER.");
18153
+ }
17861
18154
  dropTable(tableName) {
17862
18155
  return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)} CASCADE`;
17863
18156
  }
@@ -18005,7 +18298,42 @@ class SQLiteDriver {
18005
18298
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${parts.join(" ")};`;
18006
18299
  }
18007
18300
  modifyColumn(tableName, column) {
18008
- return `-- SQLite does not support ALTER COLUMN. Manual table recreation needed to change ${this.quoteIdentifier(tableName)}.${this.quoteIdentifier(column.name)} type`;
18301
+ return `-- SQLite does not support ALTER COLUMN; a table rebuild is required to change ${this.quoteIdentifier(tableName)}.${this.quoteIdentifier(column.name)}`;
18302
+ }
18303
+ renameColumn(tableName, from, to) {
18304
+ return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
18305
+ }
18306
+ renameTable(from, to) {
18307
+ return `ALTER TABLE ${this.quoteIdentifier(from)} RENAME TO ${this.quoteIdentifier(to)};`;
18308
+ }
18309
+ rebuildTable(spec) {
18310
+ const { target, tempName, columnSource } = spec;
18311
+ const q = (id) => this.quoteIdentifier(id);
18312
+ const columnsSql = target.columns.map((c) => this.renderColumn(c)).join(`,
18313
+ `);
18314
+ const createTmp = `CREATE TABLE ${q(tempName)} (
18315
+ ${columnsSql}
18316
+ );`;
18317
+ const carried = target.columns.map((c) => c.name).filter((name) => columnSource[name] !== undefined);
18318
+ const statements = [
18319
+ "PRAGMA foreign_keys=OFF;",
18320
+ "BEGIN;",
18321
+ createTmp
18322
+ ];
18323
+ if (carried.length > 0) {
18324
+ const insertCols = carried.map(q).join(", ");
18325
+ const selectCols = carried.map((name) => q(columnSource[name])).join(", ");
18326
+ statements.push(`INSERT INTO ${q(tempName)} (${insertCols}) SELECT ${selectCols} FROM ${q(target.table)};`);
18327
+ }
18328
+ statements.push(`DROP TABLE ${q(target.table)};`);
18329
+ statements.push(`ALTER TABLE ${q(tempName)} RENAME TO ${q(target.table)};`);
18330
+ for (const idx of target.indexes)
18331
+ statements.push(this.createIndex(target.table, idx));
18332
+ statements.push("PRAGMA foreign_key_check;");
18333
+ statements.push("COMMIT;");
18334
+ statements.push("PRAGMA foreign_keys=ON;");
18335
+ return statements.join(`
18336
+ `);
18009
18337
  }
18010
18338
  dropTable(tableName) {
18011
18339
  return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;
@@ -18172,6 +18500,19 @@ async function generateMigration(dir, opts = {}) {
18172
18500
  }
18173
18501
  } catch {}
18174
18502
  }
18503
+ if (!previous || opts.fromDb) {
18504
+ try {
18505
+ const livePlan = await buildPlanFromDatabase(dialect);
18506
+ const modelTables = new Set(plan.tables.map((t) => t.table));
18507
+ const scoped = livePlan.tables.filter((t) => modelTables.has(t.table));
18508
+ if (scoped.length > 0) {
18509
+ previous = { dialect: livePlan.dialect, tables: scoped };
18510
+ info(`-- No snapshot; reconciling against live database (${scoped.length} matching table${scoped.length === 1 ? "" : "s"})`);
18511
+ }
18512
+ } catch (err) {
18513
+ info(`-- Could not introspect live database, treating as no previous state: ${err instanceof Error ? err.message : String(err)}`);
18514
+ }
18515
+ }
18175
18516
  if (!previous) {
18176
18517
  info("-- No previous snapshot found, generating full migration");
18177
18518
  }
@@ -18179,7 +18520,9 @@ async function generateMigration(dir, opts = {}) {
18179
18520
  } else {
18180
18521
  info("-- Full migration requested, ignoring any previous state");
18181
18522
  }
18182
- const sqlStatements = opts.full ? generateSql(plan) : generateDiffSql(previous, plan);
18523
+ const diff = opts.full ? { statements: generateSql(plan, { dryRun: opts.dryRun }), operations: plan.tables.map((t) => ({ kind: "create_table", table: t.table, destructive: false, sql: "" })) } : generateDiffOperations(previous, plan, { applyRenames: opts.applyRenames, dryRun: opts.dryRun });
18524
+ const sqlStatements = diff.statements;
18525
+ const operations = diff.operations;
18183
18526
  const sql = sqlStatements.join(`
18184
18527
  `);
18185
18528
  const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER|DROP)\b/i.test(stmt));
@@ -18198,8 +18541,9 @@ async function generateMigration(dir, opts = {}) {
18198
18541
  throw err;
18199
18542
  }
18200
18543
  }
18201
- savePlanSnapshot(workspaceRoot, dialect, plan);
18202
- return { sql, sqlStatements, hasChanges, plan };
18544
+ if (!opts.dryRun)
18545
+ savePlanSnapshot(workspaceRoot, dialect, plan);
18546
+ return { sql, sqlStatements, hasChanges, plan, operations };
18203
18547
  }
18204
18548
  async function executeMigration(dir) {
18205
18549
  if (!dir) {
@@ -18340,11 +18684,15 @@ async function resetDatabase(dir, opts = {}) {
18340
18684
  } else {
18341
18685
  info("-- No enum types found to drop");
18342
18686
  }
18343
- try {
18344
- await deleteMigrationFiles(dir, workspaceRoot, opts);
18345
- } catch (err) {
18346
- console.error(err);
18347
- info("-- Could not clean up migration files");
18687
+ if (tableNames.length > 0) {
18688
+ try {
18689
+ await deleteMigrationFiles(dir, workspaceRoot, opts);
18690
+ } catch (err) {
18691
+ console.error(err);
18692
+ info("-- Could not clean up migration files");
18693
+ }
18694
+ } else {
18695
+ info("-- No models directory found; keeping committed migration files in place");
18348
18696
  }
18349
18697
  try {
18350
18698
  await clearGeneratedDirectory(workspaceRoot);
@@ -18448,6 +18796,7 @@ var init_migrate = __esm(() => {
18448
18796
  init_db();
18449
18797
  init_drivers();
18450
18798
  init_src2();
18799
+ init_introspect_db();
18451
18800
  });
18452
18801
 
18453
18802
  // src/actions/migrate-generate.ts
@@ -27802,11 +28151,12 @@ function buildMigrationPlan2(models, options) {
27802
28151
  }
27803
28152
  return { dialect: options.dialect, tables };
27804
28153
  }
27805
- function generateSql(plan) {
28154
+ function generateSql(plan, opts = {}) {
27806
28155
  migrationCounter = 0;
27807
28156
  migrationsCreatedCount = 0;
27808
28157
  migrationsUpdatedCount = 0;
27809
28158
  useDeterministicNames = true;
28159
+ const emit = opts.dryRun ? () => false : createMigrationFile;
27810
28160
  const statements = [];
27811
28161
  const driver = getDialectDriver(plan.dialect);
27812
28162
  for (const t2 of plan.tables) {
@@ -27830,7 +28180,7 @@ function generateSql(plan) {
27830
28180
  const combinedStatement = tableStatements.join(`
27831
28181
 
27832
28182
  `);
27833
- createMigrationFile(combinedStatement, `create-${t2.table}-table`);
28183
+ emit(combinedStatement, `create-${t2.table}-table`);
27834
28184
  }
27835
28185
  for (const t2 of plan.tables) {
27836
28186
  for (const c2 of t2.columns) {
@@ -27839,7 +28189,7 @@ function generateSql(plan) {
27839
28189
  if (!alterTableStatement)
27840
28190
  continue;
27841
28191
  statements.push(alterTableStatement);
27842
- createMigrationFile(alterTableStatement, `alter-${t2.table}-${c2.name}`);
28192
+ emit(alterTableStatement, `alter-${t2.table}-${c2.name}`);
27843
28193
  }
27844
28194
  }
27845
28195
  }
@@ -27847,7 +28197,7 @@ function generateSql(plan) {
27847
28197
  for (const idx of t2.indexes) {
27848
28198
  const createIndexStatement = driver.createIndex(t2.table, idx);
27849
28199
  statements.push(createIndexStatement);
27850
- createMigrationFile(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
28200
+ emit(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
27851
28201
  }
27852
28202
  }
27853
28203
  const totalChanges = migrationsCreatedCount + migrationsUpdatedCount;
@@ -27898,31 +28248,138 @@ function mapColumnsByName(columns) {
27898
28248
  map[c2.name] = c2;
27899
28249
  return map;
27900
28250
  }
27901
- function columnsAreDifferent(col1, col2) {
27902
- if (col1.type !== col2.type)
28251
+ function canonicalStorageType(col, dialect) {
28252
+ if (dialect === "sqlite") {
28253
+ if (col.name.endsWith("_id"))
28254
+ return "INTEGER";
28255
+ switch (col.type) {
28256
+ case "string":
28257
+ case "text":
28258
+ case "date":
28259
+ case "datetime":
28260
+ case "json":
28261
+ case "enum":
28262
+ return "TEXT";
28263
+ case "boolean":
28264
+ case "integer":
28265
+ case "bigint":
28266
+ return "INTEGER";
28267
+ case "float":
28268
+ case "double":
28269
+ case "decimal":
28270
+ return "REAL";
28271
+ default:
28272
+ return "TEXT";
28273
+ }
28274
+ }
28275
+ return col.type;
28276
+ }
28277
+ function canonicalizeDefault(col) {
28278
+ if (!col.hasDefault || col.defaultValue === undefined)
28279
+ return;
28280
+ const dv = col.defaultValue;
28281
+ if (dv instanceof Date)
28282
+ return dv.toISOString();
28283
+ if (typeof dv === "boolean")
28284
+ return dv ? "1" : "0";
28285
+ if (typeof dv === "bigint" || typeof dv === "number")
28286
+ return String(dv);
28287
+ let s2 = String(dv).trim();
28288
+ s2 = s2.replace(/::[\w ]+$/i, "").trim();
28289
+ if (s2.startsWith("'") && s2.endsWith("'") || s2.startsWith('"') && s2.endsWith('"'))
28290
+ s2 = s2.slice(1, -1);
28291
+ const upper = s2.toUpperCase();
28292
+ const sqlFns = ["CURRENT_TIMESTAMP", "NOW()", "CURRENT_DATE", "CURRENT_TIME", "NULL", "TRUE", "FALSE"];
28293
+ if (sqlFns.includes(upper))
28294
+ return upper === "NOW()" ? "CURRENT_TIMESTAMP" : upper;
28295
+ if (col.type === "boolean") {
28296
+ if (/^(?:1|t|true|yes)$/i.test(s2))
28297
+ return "1";
28298
+ if (/^(?:0|f|false|no)$/i.test(s2))
28299
+ return "0";
28300
+ }
28301
+ if (/^-?\d+(?:\.\d+)?$/.test(s2))
28302
+ return String(Number(s2));
28303
+ return s2;
28304
+ }
28305
+ function columnsAreDifferent(col1, col2, dialect) {
28306
+ if (canonicalStorageType(col1, dialect) !== canonicalStorageType(col2, dialect))
27903
28307
  return true;
27904
28308
  if (col1.isNullable !== col2.isNullable)
27905
28309
  return true;
27906
28310
  if (col1.hasDefault !== col2.hasDefault)
27907
28311
  return true;
27908
- if (col1.defaultValue !== col2.defaultValue)
28312
+ if (canonicalizeDefault(col1) !== canonicalizeDefault(col2))
27909
28313
  return true;
27910
28314
  if (col1.isUnique !== col2.isUnique)
27911
28315
  return true;
27912
28316
  if (col1.type === "enum" && col2.type === "enum") {
27913
- const enum1 = (col1.enumValues || []).sort().join(",");
27914
- const enum2 = (col2.enumValues || []).sort().join(",");
28317
+ const enum1 = (col1.enumValues || []).slice().sort().join(",");
28318
+ const enum2 = (col2.enumValues || []).slice().sort().join(",");
27915
28319
  if (enum1 !== enum2)
27916
28320
  return true;
27917
28321
  }
27918
28322
  return false;
27919
28323
  }
28324
+ function columnSignature(col, dialect) {
28325
+ return JSON.stringify([
28326
+ canonicalStorageType(col, dialect),
28327
+ col.isPrimaryKey,
28328
+ col.isUnique,
28329
+ col.isNullable,
28330
+ col.hasDefault,
28331
+ canonicalizeDefault(col) ?? null,
28332
+ (col.enumValues ?? []).slice().sort(),
28333
+ col.references ? [col.references.table, col.references.column, col.references.onDelete ?? null, col.references.onUpdate ?? null] : null
28334
+ ]);
28335
+ }
28336
+ function detectColumnRenames(prevCols, currCols, dialect) {
28337
+ const removed = Object.keys(prevCols).filter((n2) => !currCols[n2]);
28338
+ const added = Object.keys(currCols).filter((n2) => !prevCols[n2]);
28339
+ const sigRemoved = new Map;
28340
+ const sigAdded = new Map;
28341
+ for (const n2 of removed) {
28342
+ const s2 = columnSignature(prevCols[n2], dialect);
28343
+ const list = sigRemoved.get(s2) ?? [];
28344
+ list.push(n2);
28345
+ sigRemoved.set(s2, list);
28346
+ }
28347
+ for (const n2 of added) {
28348
+ const s2 = columnSignature(currCols[n2], dialect);
28349
+ const list = sigAdded.get(s2) ?? [];
28350
+ list.push(n2);
28351
+ sigAdded.set(s2, list);
28352
+ }
28353
+ const renames = [];
28354
+ const usedRemoved = new Set;
28355
+ const usedAdded = new Set;
28356
+ for (const [sig, rem] of sigRemoved) {
28357
+ const add = sigAdded.get(sig);
28358
+ if (rem.length === 1 && add && add.length === 1) {
28359
+ renames.push({ from: rem[0], to: add[0] });
28360
+ usedRemoved.add(rem[0]);
28361
+ usedAdded.add(add[0]);
28362
+ }
28363
+ }
28364
+ return {
28365
+ renames,
28366
+ removed: removed.filter((n2) => !usedRemoved.has(n2)),
28367
+ added: added.filter((n2) => !usedAdded.has(n2))
28368
+ };
28369
+ }
28370
+ function isColumnConstrained(table, columnName) {
28371
+ const col = table.columns.find((c2) => c2.name === columnName);
28372
+ if (col && (col.isPrimaryKey || col.isUnique || col.references))
28373
+ return true;
28374
+ return table.indexes.some((idx) => idx.columns.includes(columnName));
28375
+ }
27920
28376
  function referencesAreDifferent(r1, r2) {
27921
28377
  if (Boolean(r1) !== Boolean(r2))
27922
28378
  return true;
27923
28379
  if (!r1 || !r2)
27924
28380
  return false;
27925
- return r1.table !== r2.table || r1.column !== r2.column || r1.onDelete !== r2.onDelete || r1.onUpdate !== r2.onUpdate;
28381
+ const act = (a2) => (a2 ?? "no action").toLowerCase();
28382
+ return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
27926
28383
  }
27927
28384
  function mapIndexesByKey(indexes) {
27928
28385
  const map = {};
@@ -27932,22 +28389,35 @@ function mapIndexesByKey(indexes) {
27932
28389
  }
27933
28390
  return map;
27934
28391
  }
27935
- function generateDiffSql(previous, next) {
27936
- if (!previous || previous.dialect !== next.dialect)
27937
- return generateSql(next);
28392
+ function generateDiffOperations(previous, next, opts = {}) {
28393
+ if (!previous || previous.dialect !== next.dialect) {
28394
+ const statements2 = generateSql(next, { dryRun: opts.dryRun });
28395
+ const operations2 = next.tables.map((t2) => ({
28396
+ kind: "create_table",
28397
+ table: t2.table,
28398
+ destructive: false,
28399
+ sql: ""
28400
+ }));
28401
+ return { statements: statements2, operations: operations2 };
28402
+ }
28403
+ const applyRenames = opts.applyRenames !== false;
28404
+ const emit = opts.dryRun ? () => false : createMigrationFile;
27938
28405
  migrationCounter = 0;
27939
28406
  migrationsCreatedCount = 0;
27940
28407
  migrationsUpdatedCount = 0;
27941
28408
  useDeterministicNames = false;
27942
28409
  const chunks = [];
28410
+ const operations = [];
27943
28411
  const driver = getDialectDriver(next.dialect);
28412
+ const dialect = next.dialect;
27944
28413
  const prevTables = mapTablesByName(previous.tables);
27945
28414
  const nextTables = mapTablesByName(next.tables);
27946
28415
  for (const tableName of Object.keys(prevTables)) {
27947
28416
  if (!nextTables[tableName]) {
27948
28417
  const dropTableStatement = driver.dropTable(tableName);
27949
28418
  chunks.push(dropTableStatement);
27950
- createMigrationFile(dropTableStatement, `drop-${tableName}-table`);
28419
+ operations.push({ kind: "drop_table", table: tableName, destructive: true, sql: dropTableStatement });
28420
+ emit(dropTableStatement, `drop-${tableName}-table`);
27951
28421
  info2(`-- Detected dropped table: ${tableName}`);
27952
28422
  }
27953
28423
  }
@@ -27971,10 +28441,11 @@ function generateDiffSql(previous, next) {
27971
28441
  const createTableStatement = driver.createTable(t2);
27972
28442
  tableStatements.push(createTableStatement);
27973
28443
  chunks.push(...tableStatements);
28444
+ operations.push({ kind: "create_table", table: t2.table, destructive: false, sql: createTableStatement });
27974
28445
  const combinedStatement = tableStatements.join(`
27975
28446
 
27976
28447
  `);
27977
- createMigrationFile(combinedStatement, `create-${t2.table}-table`);
28448
+ emit(combinedStatement, `create-${t2.table}-table`);
27978
28449
  }
27979
28450
  }
27980
28451
  for (const tableName of Object.keys(nextTables)) {
@@ -27983,7 +28454,8 @@ function generateDiffSql(previous, next) {
27983
28454
  for (const idx of t2.indexes) {
27984
28455
  const createIndexStatement = driver.createIndex(t2.table, idx);
27985
28456
  chunks.push(createIndexStatement);
27986
- createMigrationFile(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
28457
+ operations.push({ kind: "create_index", table: t2.table, column: idx.name, destructive: false, sql: createIndexStatement });
28458
+ emit(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
27987
28459
  }
27988
28460
  }
27989
28461
  }
@@ -27996,7 +28468,8 @@ function generateDiffSql(previous, next) {
27996
28468
  if (!alterTableStatement)
27997
28469
  continue;
27998
28470
  chunks.push(alterTableStatement);
27999
- createMigrationFile(alterTableStatement, `alter-${t2.table}-${c2.name}`);
28471
+ operations.push({ kind: "add_foreign_key", table: t2.table, column: c2.name, destructive: false, sql: alterTableStatement });
28472
+ emit(alterTableStatement, `alter-${t2.table}-${c2.name}`);
28000
28473
  }
28001
28474
  }
28002
28475
  }
@@ -28018,7 +28491,8 @@ function generateDiffSql(previous, next) {
28018
28491
  const createEnumStatement = driver.createEnumType(enumTypeName, c2.enumValues);
28019
28492
  if (createEnumStatement) {
28020
28493
  chunks.push(createEnumStatement);
28021
- createMigrationFile(createEnumStatement, `create-${enumTypeName}-enum`);
28494
+ operations.push({ kind: "create_enum", table: curr.table, column: c2.name, destructive: false, sql: createEnumStatement });
28495
+ emit(createEnumStatement, `create-${enumTypeName}-enum`);
28022
28496
  }
28023
28497
  enumTypes.add(enumTypeName);
28024
28498
  }
@@ -28035,6 +28509,52 @@ function generateDiffSql(previous, next) {
28035
28509
  const currCols = mapColumnsByName(curr.columns);
28036
28510
  const prevIdx = mapIndexesByKey(prev.indexes);
28037
28511
  const currIdx = mapIndexesByKey(curr.indexes);
28512
+ let renames = [];
28513
+ let removedCols = Object.keys(prevCols).filter((n2) => !currCols[n2]);
28514
+ let addedCols = Object.keys(currCols).filter((n2) => !prevCols[n2]);
28515
+ if (applyRenames) {
28516
+ const detected = detectColumnRenames(prevCols, currCols, dialect);
28517
+ renames = detected.renames;
28518
+ removedCols = detected.removed;
28519
+ addedCols = detected.added;
28520
+ }
28521
+ const modifiedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && columnsAreDifferent(prevCols[n2], currCols[n2], dialect));
28522
+ const fkChangedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && referencesAreDifferent(prevCols[n2].references, currCols[n2].references));
28523
+ if (dialect === "sqlite") {
28524
+ const needsRebuild = modifiedCols.length > 0 || fkChangedCols.length > 0 || removedCols.some((name) => isColumnConstrained(prev, name));
28525
+ if (needsRebuild) {
28526
+ const columnSource = {};
28527
+ const renameTo = new Map(renames.map((r2) => [r2.to, r2.from]));
28528
+ for (const c2 of curr.columns) {
28529
+ if (prevCols[c2.name])
28530
+ columnSource[c2.name] = c2.name;
28531
+ else if (renameTo.has(c2.name))
28532
+ columnSource[c2.name] = renameTo.get(c2.name);
28533
+ }
28534
+ let tempName = `_qb_tmp_${curr.table}`;
28535
+ let guard = 0;
28536
+ while ((prevTables[tempName] || nextTables[tempName]) && guard < 100) {
28537
+ guard += 1;
28538
+ tempName = `_qb_tmp_${curr.table}_${guard}`;
28539
+ }
28540
+ const rebuildStatement = driver.rebuildTable({ target: curr, tempName, columnSource });
28541
+ chunks.push(rebuildStatement);
28542
+ const typeChanged = modifiedCols.some((n2) => prevCols[n2].type !== currCols[n2].type);
28543
+ operations.push({
28544
+ kind: "rebuild_table",
28545
+ table: curr.table,
28546
+ destructive: removedCols.length > 0 || typeChanged,
28547
+ sql: rebuildStatement
28548
+ });
28549
+ for (const r2 of renames)
28550
+ operations.push({ kind: "rename_column", table: curr.table, from: r2.from, to: r2.to, destructive: false, confidence: "high", sql: rebuildStatement });
28551
+ for (const name of removedCols)
28552
+ operations.push({ kind: "drop_column", table: curr.table, column: name, destructive: true, sql: rebuildStatement });
28553
+ info2(`-- Detected SQLite table rebuild required: ${curr.table}`);
28554
+ emit(rebuildStatement, `alter-${curr.table}-table`);
28555
+ continue;
28556
+ }
28557
+ }
28038
28558
  const tableChanges = [];
28039
28559
  let hasChanges = false;
28040
28560
  for (const key of Object.keys(prevIdx)) {
@@ -28043,51 +28563,70 @@ function generateDiffSql(previous, next) {
28043
28563
  const dropIndexStatement = driver.dropIndex(curr.table, idx.name);
28044
28564
  tableChanges.push(dropIndexStatement);
28045
28565
  chunks.push(dropIndexStatement);
28566
+ operations.push({ kind: "drop_index", table: curr.table, column: idx.name, destructive: false, sql: dropIndexStatement });
28046
28567
  info2(`-- Detected dropped index: ${idx.name} from ${curr.table}`);
28047
28568
  hasChanges = true;
28048
28569
  }
28049
28570
  }
28050
- for (const colName of Object.keys(prevCols)) {
28051
- if (!currCols[colName]) {
28052
- const dropColumnStatement = driver.dropColumn(curr.table, colName);
28053
- tableChanges.push(dropColumnStatement);
28054
- chunks.push(dropColumnStatement);
28055
- info2(`-- Detected dropped column: ${curr.table}.${colName}`);
28056
- hasChanges = true;
28057
- }
28058
- }
28059
- for (const colName of Object.keys(currCols)) {
28060
- if (prevCols[colName] && currCols[colName]) {
28061
- const prevCol = prevCols[colName];
28062
- const currCol = currCols[colName];
28063
- if (columnsAreDifferent(prevCol, currCol)) {
28064
- const modifyColumnStatement = driver.modifyColumn(curr.table, currCol);
28065
- tableChanges.push(modifyColumnStatement);
28066
- chunks.push(modifyColumnStatement);
28067
- info2(`-- Detected column type change: ${curr.table}.${colName} (${prevCol.type} -> ${currCol.type})`);
28068
- hasChanges = true;
28069
- }
28070
- if (referencesAreDifferent(prevCol.references, currCol.references) && currCol.references) {
28071
- const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
28072
- tableChanges.push(addFkStatement);
28073
- chunks.push(addFkStatement);
28074
- info2(`-- Detected foreign-key change: ${curr.table}.${colName} -> ${currCol.references.table}(${currCol.references.column})${currCol.references.onDelete ? ` ON DELETE ${currCol.references.onDelete}` : ""}`);
28075
- hasChanges = true;
28076
- }
28077
- }
28571
+ for (const r2 of renames) {
28572
+ const renameStatement = driver.renameColumn(curr.table, r2.from, r2.to);
28573
+ tableChanges.push(renameStatement);
28574
+ chunks.push(renameStatement);
28575
+ operations.push({ kind: "rename_column", table: curr.table, from: r2.from, to: r2.to, destructive: false, confidence: "high", sql: renameStatement });
28576
+ info2(`-- Detected renamed column: ${curr.table}.${r2.from} -> ${r2.to}`);
28577
+ hasChanges = true;
28578
+ }
28579
+ for (const colName of removedCols) {
28580
+ const dropColumnStatement = driver.dropColumn(curr.table, colName);
28581
+ tableChanges.push(dropColumnStatement);
28582
+ chunks.push(dropColumnStatement);
28583
+ operations.push({ kind: "drop_column", table: curr.table, column: colName, destructive: true, sql: dropColumnStatement });
28584
+ info2(`-- Detected dropped column: ${curr.table}.${colName}`);
28585
+ hasChanges = true;
28586
+ }
28587
+ for (const colName of modifiedCols) {
28588
+ const prevCol = prevCols[colName];
28589
+ const currCol = currCols[colName];
28590
+ const modifyColumnStatement = driver.modifyColumn(curr.table, currCol);
28591
+ tableChanges.push(modifyColumnStatement);
28592
+ chunks.push(modifyColumnStatement);
28593
+ operations.push({
28594
+ kind: "modify_column",
28595
+ table: curr.table,
28596
+ column: colName,
28597
+ destructive: prevCol.type !== currCol.type,
28598
+ sql: modifyColumnStatement
28599
+ });
28600
+ info2(`-- Detected column change: ${curr.table}.${colName} (${prevCol.type} -> ${currCol.type})`);
28601
+ hasChanges = true;
28078
28602
  }
28079
- for (const colName of Object.keys(currCols)) {
28080
- if (!prevCols[colName]) {
28081
- const c2 = currCols[colName];
28082
- const addColumnStatement = driver.addColumn(curr.table, c2);
28083
- tableChanges.push(addColumnStatement);
28084
- chunks.push(addColumnStatement);
28085
- info2(`-- Detected new column: ${curr.table}.${c2.name}`);
28086
- hasChanges = true;
28087
- if (c2.references) {
28088
- const addFkStatement = driver.addForeignKey(curr.table, c2.name, c2.references.table, c2.references.column, c2.references.onDelete, c2.references.onUpdate);
28603
+ for (const colName of fkChangedCols) {
28604
+ const currCol = currCols[colName];
28605
+ if (!currCol.references)
28606
+ continue;
28607
+ const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
28608
+ if (!addFkStatement)
28609
+ continue;
28610
+ tableChanges.push(addFkStatement);
28611
+ chunks.push(addFkStatement);
28612
+ operations.push({ kind: "add_foreign_key", table: curr.table, column: colName, destructive: false, sql: addFkStatement });
28613
+ info2(`-- Detected foreign-key change: ${curr.table}.${colName} -> ${currCol.references.table}(${currCol.references.column})`);
28614
+ hasChanges = true;
28615
+ }
28616
+ for (const colName of addedCols) {
28617
+ const c2 = currCols[colName];
28618
+ const addColumnStatement = driver.addColumn(curr.table, c2);
28619
+ tableChanges.push(addColumnStatement);
28620
+ chunks.push(addColumnStatement);
28621
+ operations.push({ kind: "add_column", table: curr.table, column: c2.name, destructive: false, sql: addColumnStatement });
28622
+ info2(`-- Detected new column: ${curr.table}.${c2.name}`);
28623
+ hasChanges = true;
28624
+ if (c2.references) {
28625
+ const addFkStatement = driver.addForeignKey(curr.table, c2.name, c2.references.table, c2.references.column, c2.references.onDelete, c2.references.onUpdate);
28626
+ if (addFkStatement) {
28089
28627
  tableChanges.push(addFkStatement);
28090
28628
  chunks.push(addFkStatement);
28629
+ operations.push({ kind: "add_foreign_key", table: curr.table, column: c2.name, destructive: false, sql: addFkStatement });
28091
28630
  }
28092
28631
  }
28093
28632
  }
@@ -28097,6 +28636,7 @@ function generateDiffSql(previous, next) {
28097
28636
  const createIndexStatement = driver.createIndex(curr.table, idx);
28098
28637
  tableChanges.push(createIndexStatement);
28099
28638
  chunks.push(createIndexStatement);
28639
+ operations.push({ kind: "create_index", table: curr.table, column: idx.name, destructive: false, sql: createIndexStatement });
28100
28640
  info2(`-- Detected new index: ${idx.name} in ${curr.table}`);
28101
28641
  hasChanges = true;
28102
28642
  }
@@ -28105,7 +28645,7 @@ function generateDiffSql(previous, next) {
28105
28645
  const combinedStatement = tableChanges.join(`
28106
28646
 
28107
28647
  `);
28108
- createMigrationFile(combinedStatement, `alter-${curr.table}-table`);
28648
+ emit(combinedStatement, `alter-${curr.table}-table`);
28109
28649
  }
28110
28650
  }
28111
28651
  const totalChanges = migrationsCreatedCount + migrationsUpdatedCount;
@@ -28119,9 +28659,11 @@ function generateDiffSql(previous, next) {
28119
28659
  parts.push(`${migrationsUpdatedCount} updated`);
28120
28660
  info2(`-- Migration files: ${parts.join(", ")}`);
28121
28661
  }
28122
- if (chunks.length === 0)
28123
- return ["-- No changes detected"];
28124
- return chunks;
28662
+ const statements = chunks.length === 0 ? ["-- No changes detected"] : chunks;
28663
+ return { statements, operations };
28664
+ }
28665
+ function generateDiffSql(previous, next, opts = {}) {
28666
+ return generateDiffOperations(previous, next, opts).statements;
28125
28667
  }
28126
28668
  var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true;
28127
28669
  var init_migrations = __esm(() => {
@@ -28237,6 +28779,7 @@ export {
28237
28779
  generateMigration,
28238
28780
  generateDiffSqlString,
28239
28781
  generateDiffSql,
28782
+ generateDiffOperations,
28240
28783
  relationDiagram as generateDiagram,
28241
28784
  generateAccessPatterns,
28242
28785
  freshDatabase,
@@ -28282,6 +28825,8 @@ export {
28282
28825
  clearQueryCache,
28283
28826
  clearModelRegistry,
28284
28827
  checkSchema,
28828
+ canonicalizeDefault,
28829
+ canonicalStorageType,
28285
28830
  cacheStats,
28286
28831
  cacheConfig,
28287
28832
  cacheClear,