bun-query-builder 0.1.63 → 0.2.0

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/bin/cli.js CHANGED
@@ -11641,9 +11641,18 @@ var init_dist = __esm(() => {
11641
11641
  });
11642
11642
 
11643
11643
  // src/config.ts
11644
+ import process19 from "process";
11644
11645
  function isMysqlLike(dialect = config5.dialect) {
11645
11646
  return dialect === "mysql" || dialect === "singlestore";
11646
11647
  }
11648
+ function resolveDialect(explicit) {
11649
+ if (explicit)
11650
+ return explicit;
11651
+ const fromEnv = process19.env.DB_DIALECT || process19.env.DB_CONNECTION;
11652
+ if (fromEnv)
11653
+ return fromEnv;
11654
+ return config5.dialect || "postgres";
11655
+ }
11647
11656
  function getPlaceholder(index) {
11648
11657
  if (config5.dialect === "postgres") {
11649
11658
  return `$${index}`;
@@ -11802,7 +11811,7 @@ var {SQL } = globalThis.Bun;
11802
11811
  import { Database } from "bun:sqlite";
11803
11812
  import { mkdirSync as mkdirSync5 } from "fs";
11804
11813
  import { dirname as dirname5 } from "path";
11805
- import process19 from "process";
11814
+ import process21 from "process";
11806
11815
 
11807
11816
  class SQLiteWrapper {
11808
11817
  db;
@@ -12080,9 +12089,13 @@ function sqliteFileFromUrl(url) {
12080
12089
  const path = stripped.startsWith("///") ? stripped.slice(2) : stripped.startsWith("//") ? stripped.slice(2) : stripped;
12081
12090
  return path || ":memory:";
12082
12091
  }
12092
+ function resolveDatabaseName(dbConfig = config5.database) {
12093
+ const raw = process21.env.DB_DATABASE || dbConfig?.database || "";
12094
+ return String(raw).replace(/^['"]|['"]$/g, "");
12095
+ }
12083
12096
  function createConnectionString(dialect, dbConfig) {
12084
- if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process19.env.DB_CONNECTION === dialect) {
12085
- const e = process19.env;
12097
+ if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process21.env.DB_CONNECTION === dialect) {
12098
+ const e = process21.env;
12086
12099
  const envDb = (e.DB_DATABASE || dbConfig.database || "").replace(/^['"]|['"]$/g, "");
12087
12100
  const envUser = e.DB_USERNAME || dbConfig.username;
12088
12101
  const envPass = e.DB_PASSWORD ?? dbConfig.password ?? "";
@@ -12096,7 +12109,7 @@ function createConnectionString(dialect, dbConfig) {
12096
12109
  return dialect === "sqlite" ? sqliteFileFromUrl(dbConfig.url) : dbConfig.url;
12097
12110
  }
12098
12111
  const { database, username, password, host = "localhost", port, ssl } = dbConfig;
12099
- const sslQ = ssl || process19.env.DB_SSL === "true" || process19.env.DB_SSL === "1" ? "?ssl=true" : "";
12112
+ const sslQ = ssl || process21.env.DB_SSL === "true" || process21.env.DB_SSL === "1" ? "?ssl=true" : "";
12100
12113
  switch (dialect) {
12101
12114
  case "postgres":
12102
12115
  return `postgres://${username}:${password}@${host}${port ? `:${port}` : ""}/${database}${sslQ}`;
@@ -12370,6 +12383,17 @@ var init_benchmark = __esm(() => {
12370
12383
  });
12371
12384
 
12372
12385
  // src/inflect.ts
12386
+ function toTableName(modelName) {
12387
+ const snake = String(modelName).replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
12388
+ if (snake.endsWith("y") && !/[aeou]y$/.test(snake))
12389
+ return `${snake.slice(0, -1)}ies`;
12390
+ if (snake.endsWith("s") || snake.endsWith("x") || snake.endsWith("ch") || snake.endsWith("sh"))
12391
+ return `${snake}es`;
12392
+ return `${snake}s`;
12393
+ }
12394
+ function tableNameFor(model) {
12395
+ return typeof model?.table === "string" && model.table.length > 0 ? model.table : toTableName(String(model?.name ?? ""));
12396
+ }
12373
12397
  function stripTrailingS(name) {
12374
12398
  return name.endsWith("s") ? name.slice(0, -1) : name;
12375
12399
  }
@@ -16590,7 +16614,7 @@ var init_cache = __esm(() => {
16590
16614
  });
16591
16615
 
16592
16616
  // src/actions/console.ts
16593
- import process21 from "process";
16617
+ import process22 from "process";
16594
16618
  import { createInterface } from "readline";
16595
16619
  async function startConsole() {
16596
16620
  console.log("-- Query Builder Interactive Console");
@@ -16599,8 +16623,8 @@ async function startConsole() {
16599
16623
  console.log();
16600
16624
  const qb = createQueryBuilder();
16601
16625
  const rl = createInterface({
16602
- input: process21.stdin,
16603
- output: process21.stdout,
16626
+ input: process22.stdin,
16627
+ output: process22.stdout,
16604
16628
  prompt: "qb> "
16605
16629
  });
16606
16630
  let multilineBuffer = "";
@@ -16651,7 +16675,7 @@ Tips:
16651
16675
  if (cmd === ".exit" || cmd === ".quit") {
16652
16676
  console.log("Goodbye!");
16653
16677
  rl.close();
16654
- process21.exit(0);
16678
+ process22.exit(0);
16655
16679
  } else if (cmd === ".help") {
16656
16680
  console.log(helpText);
16657
16681
  } else if (cmd === ".clear") {
@@ -16722,7 +16746,7 @@ Tips:
16722
16746
  rl.on("close", () => {
16723
16747
  console.log(`
16724
16748
  Goodbye!`);
16725
- process21.exit(0);
16749
+ process22.exit(0);
16726
16750
  });
16727
16751
  rl.prompt();
16728
16752
  }
@@ -17100,9 +17124,8 @@ var init_db_info = __esm(() => {
17100
17124
  });
17101
17125
 
17102
17126
  // src/actions/db-optimize.ts
17103
- import process22 from "process";
17104
17127
  async function dbOptimize(options = {}) {
17105
- const dialect = options.dialect || process22.env.DB_DIALECT || config5.dialect || "postgres";
17128
+ const dialect = resolveDialect(options.dialect);
17106
17129
  const aggressive = options.aggressive || false;
17107
17130
  if (options.verbose) {
17108
17131
  console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
@@ -17142,7 +17165,7 @@ async function dbOptimize(options = {}) {
17142
17165
  await bunSql`ANALYZE TABLE ${bunSql(table)}`;
17143
17166
  }
17144
17167
  } else {
17145
- const dbName = process22.env.DB_NAME || "test";
17168
+ const dbName = resolveDatabaseName();
17146
17169
  const tables = await bunSql`
17147
17170
  SELECT table_name
17148
17171
  FROM information_schema.tables
@@ -17195,9 +17218,8 @@ var init_db_optimize = __esm(() => {
17195
17218
  });
17196
17219
 
17197
17220
  // src/actions/db-wipe.ts
17198
- import process23 from "process";
17199
17221
  async function dbWipe(options = {}) {
17200
- const dialect = options.dialect || process23.env.DB_DIALECT || config5.dialect || "postgres";
17222
+ const dialect = resolveDialect(options.dialect);
17201
17223
  if (options.verbose) {
17202
17224
  console.log(`Wiping all tables from ${dialect} database...`);
17203
17225
  }
@@ -17211,7 +17233,7 @@ async function dbWipe(options = {}) {
17211
17233
  `;
17212
17234
  tables = result.map((row) => row.tablename);
17213
17235
  } else if (isMysqlLike(dialect)) {
17214
- const dbName = process23.env.DB_NAME || "test";
17236
+ const dbName = resolveDatabaseName();
17215
17237
  const result = await bunSql`
17216
17238
  SELECT table_name
17217
17239
  FROM information_schema.tables
@@ -17648,6 +17670,8 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17648
17670
  return "decimal";
17649
17671
  if (base === "date")
17650
17672
  return "date";
17673
+ if (base === "timestamptz" || /\bwith time zone\b/.test(t))
17674
+ return "timestamptz";
17651
17675
  if (/^(?:timestamp|datetime)/.test(base))
17652
17676
  return "datetime";
17653
17677
  if (/^time/.test(base))
@@ -17903,7 +17927,7 @@ var init_introspect_db = __esm(() => {
17903
17927
  // src/actions/make-model.ts
17904
17928
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
17905
17929
  import { dirname as dirname7, join as join7 } from "path";
17906
- import process24 from "process";
17930
+ import process23 from "process";
17907
17931
  function findWorkspaceRoot(startPath) {
17908
17932
  let currentPath = startPath;
17909
17933
  while (currentPath !== dirname7(currentPath)) {
@@ -17912,10 +17936,10 @@ function findWorkspaceRoot(startPath) {
17912
17936
  }
17913
17937
  currentPath = dirname7(currentPath);
17914
17938
  }
17915
- return process24.cwd();
17939
+ return process23.cwd();
17916
17940
  }
17917
17941
  async function makeModel(name, options = {}) {
17918
- const workspaceRoot = findWorkspaceRoot(process24.cwd());
17942
+ const workspaceRoot = findWorkspaceRoot(process23.cwd());
17919
17943
  const modelsDir = options.dir || join7(workspaceRoot, "app/Models");
17920
17944
  if (!existsSync14(modelsDir)) {
17921
17945
  mkdirSync7(modelsDir, { recursive: true });
@@ -18111,7 +18135,7 @@ class MySQLDriver {
18111
18135
  )`;
18112
18136
  }
18113
18137
  getExecutedMigrationsQuery() {
18114
- return "SELECT migration FROM migrations ORDER BY executed_at";
18138
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18115
18139
  }
18116
18140
  recordMigrationQuery() {
18117
18141
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18292,7 +18316,7 @@ class PostgresDriver {
18292
18316
  )`;
18293
18317
  }
18294
18318
  getExecutedMigrationsQuery() {
18295
- return "SELECT migration FROM migrations ORDER BY executed_at";
18319
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18296
18320
  }
18297
18321
  recordMigrationQuery() {
18298
18322
  return "INSERT INTO migrations (migration) VALUES ($1)";
@@ -18537,7 +18561,7 @@ class SQLiteDriver {
18537
18561
  )`;
18538
18562
  }
18539
18563
  getExecutedMigrationsQuery() {
18540
- return "SELECT migration FROM migrations ORDER BY executed_at";
18564
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18541
18565
  }
18542
18566
  recordMigrationQuery() {
18543
18567
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18590,6 +18614,24 @@ var init_drivers = __esm(() => {
18590
18614
  init_singlestore();
18591
18615
  });
18592
18616
 
18617
+ // src/workspace.ts
18618
+ import { existsSync as existsSync16 } from "fs";
18619
+ import { dirname as dirname8, join as join9 } from "path";
18620
+ import process24 from "process";
18621
+ function findWorkspaceRoot2(startPath = process24.cwd()) {
18622
+ let currentPath = startPath;
18623
+ while (currentPath !== dirname8(currentPath)) {
18624
+ if (existsSync16(join9(currentPath, "package.json")))
18625
+ return currentPath;
18626
+ currentPath = dirname8(currentPath);
18627
+ }
18628
+ return process24.cwd();
18629
+ }
18630
+ function getSqlDirectory(workspaceRoot) {
18631
+ return join9(workspaceRoot ?? findWorkspaceRoot2(), "database", "migrations");
18632
+ }
18633
+ var init_workspace = () => {};
18634
+
18593
18635
  // src/actions/migrate.ts
18594
18636
  var exports_migrate = {};
18595
18637
  __export(exports_migrate, {
@@ -18601,23 +18643,48 @@ __export(exports_migrate, {
18601
18643
  copyModelsToGenerated: () => copyModelsToGenerated,
18602
18644
  clearGeneratedDirectory: () => clearGeneratedDirectory
18603
18645
  });
18604
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18646
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18605
18647
  import { tmpdir } from "os";
18606
- import { join as join9 } from "path";
18648
+ import { join as join10 } from "path";
18607
18649
  import process25 from "process";
18650
+ async function settleAppliedFiles(qb, dialect, files) {
18651
+ if (files.length === 0)
18652
+ return;
18653
+ const sqlDir = ensureSqlDirectory(getWorkspaceRoot());
18654
+ try {
18655
+ await createMigrationsTable(qb, dialect);
18656
+ for (const file2 of files) {
18657
+ await recordMigration(qb, file2, dialect);
18658
+ info(`-- \u2713 Recorded applied migration: ${file2}`);
18659
+ }
18660
+ } catch (err) {
18661
+ info(`-- Could not record applied migrations (the schema change itself succeeded): ${err instanceof Error ? err.message : String(err)}`);
18662
+ }
18663
+ }
18664
+ function isGeneratedMigration(sqlDir, file2) {
18665
+ try {
18666
+ return isGeneratedMigrationSql(readFileSync3(join10(sqlDir, file2), "utf8"));
18667
+ } catch {
18668
+ return false;
18669
+ }
18670
+ }
18671
+ function isAlreadyAppliedError(err) {
18672
+ const message = err instanceof Error ? err.message : String(err);
18673
+ return /duplicate column name/i.test(message) || /duplicate key name/i.test(message) || /\balready exists\b/i.test(message);
18674
+ }
18608
18675
  function info(message) {
18609
18676
  if (config5.verbose)
18610
18677
  console.log(message);
18611
18678
  }
18612
18679
  function getSnapshotDir(workspaceRoot) {
18613
- return join9(workspaceRoot, config5.snapshotDir || ".qb");
18680
+ return join10(workspaceRoot, config5.snapshotDir || ".qb");
18614
18681
  }
18615
18682
  function getSnapshotPath(workspaceRoot, dialect) {
18616
- return join9(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18683
+ return join10(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18617
18684
  }
18618
18685
  function loadPlanSnapshot(workspaceRoot, dialect) {
18619
18686
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18620
- if (!existsSync16(snapshotPath)) {
18687
+ if (!existsSync17(snapshotPath)) {
18621
18688
  return;
18622
18689
  }
18623
18690
  try {
@@ -18640,7 +18707,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18640
18707
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18641
18708
  const snapshotDir = getSnapshotDir(workspaceRoot);
18642
18709
  const hash = hashMigrationPlan(plan);
18643
- if (existsSync16(snapshotPath)) {
18710
+ if (existsSync17(snapshotPath)) {
18644
18711
  try {
18645
18712
  const current = JSON.parse(readFileSync3(snapshotPath, "utf8"));
18646
18713
  if (current?.hash === hash && current?.dialect === dialect && current?.plan) {
@@ -18649,7 +18716,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18649
18716
  }
18650
18717
  } catch {}
18651
18718
  }
18652
- if (!existsSync16(snapshotDir)) {
18719
+ if (!existsSync17(snapshotDir)) {
18653
18720
  mkdirSync8(snapshotDir, { recursive: true });
18654
18721
  info(`-- Created snapshot directory: ${snapshotDir}`);
18655
18722
  }
@@ -18667,11 +18734,11 @@ function saveMigrationSnapshot(plan, options = {}) {
18667
18734
  savePlanSnapshot(options.workspaceRoot ?? getWorkspaceRoot(), dialect, plan);
18668
18735
  }
18669
18736
  function getWorkspaceRoot() {
18670
- return process25.cwd();
18737
+ return findWorkspaceRoot2();
18671
18738
  }
18672
18739
  function ensureSqlDirectory(workspaceRoot) {
18673
18740
  const sqlDir = getSqlDirectory(workspaceRoot);
18674
- if (!existsSync16(sqlDir)) {
18741
+ if (!existsSync17(sqlDir)) {
18675
18742
  mkdirSync8(sqlDir, { recursive: true });
18676
18743
  info(`-- Created SQL directory: ${sqlDir}`);
18677
18744
  }
@@ -18680,21 +18747,22 @@ function ensureSqlDirectory(workspaceRoot) {
18680
18747
  async function generateMigration(dir, opts = {}) {
18681
18748
  await getConfig();
18682
18749
  if (!dir) {
18683
- dir = join9(process25.cwd(), "app/Models");
18750
+ dir = join10(process25.cwd(), "app/Models");
18684
18751
  }
18685
18752
  const dialect = opts.dialect || config5.dialect || "postgres";
18686
18753
  const workspaceRoot = getWorkspaceRoot();
18687
18754
  const models = await loadModels({ modelsDir: dir });
18688
18755
  const plan = buildMigrationPlan2(models, { dialect });
18689
18756
  let previous;
18757
+ let reconciledFromDatabase = false;
18690
18758
  if (!opts.full) {
18691
18759
  previous = loadPlanSnapshot(workspaceRoot, dialect);
18692
18760
  if (previous) {
18693
18761
  info("-- Comparing with stored model snapshot");
18694
18762
  } else {
18695
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
18763
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18696
18764
  const statePath = String(opts.state || defaultStatePath);
18697
- if (existsSync16(statePath)) {
18765
+ if (existsSync17(statePath)) {
18698
18766
  try {
18699
18767
  const raw2 = readFileSync3(statePath, "utf8");
18700
18768
  const parsed = JSON.parse(raw2);
@@ -18711,6 +18779,7 @@ async function generateMigration(dir, opts = {}) {
18711
18779
  const scoped = livePlan.tables.filter((t) => modelTables.has(t.table));
18712
18780
  if (scoped.length > 0) {
18713
18781
  previous = { dialect: livePlan.dialect, tables: scoped };
18782
+ reconciledFromDatabase = true;
18714
18783
  info(`-- No snapshot; reconciling against live database (${scoped.length} matching table${scoped.length === 1 ? "" : "s"})`);
18715
18784
  }
18716
18785
  } catch (err) {
@@ -18724,25 +18793,34 @@ async function generateMigration(dir, opts = {}) {
18724
18793
  } else {
18725
18794
  info("-- Full migration requested, ignoring any previous state");
18726
18795
  }
18727
- 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 });
18796
+ 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, {
18797
+ applyRenames: opts.applyRenames,
18798
+ dryRun: opts.dryRun,
18799
+ preserveUnknownColumns: reconciledFromDatabase
18800
+ });
18728
18801
  const sqlStatements = diff.statements;
18729
18802
  const operations = diff.operations;
18730
18803
  const sql = sqlStatements.join(`
18731
18804
  `);
18732
18805
  const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER|DROP)\b/i.test(stmt));
18733
18806
  if (opts.apply) {
18734
- const dirPath = mkdtempSync(join9(tmpdir(), "qb-migrate-"));
18735
- const filePath = join9(dirPath, "migration.sql");
18807
+ const dirPath = mkdtempSync(join10(tmpdir(), "qb-migrate-"));
18808
+ const filePath = join10(dirPath, "migration.sql");
18736
18809
  try {
18737
18810
  if (hasChanges) {
18738
18811
  writeFileSync11(filePath, sql);
18812
+ const qb = createQueryBuilder();
18813
+ await qb.file(filePath);
18739
18814
  info("-- Migration applied");
18815
+ await settleAppliedFiles(qb, dialect, lastCreatedMigrationFiles());
18740
18816
  } else {
18741
18817
  info("-- No changes; nothing to apply");
18742
18818
  }
18743
18819
  } catch (err) {
18744
18820
  console.error("-- Migration failed:", err);
18745
18821
  throw err;
18822
+ } finally {
18823
+ rmSync(dirPath, { recursive: true, force: true });
18746
18824
  }
18747
18825
  }
18748
18826
  if (!opts.dryRun)
@@ -18751,9 +18829,6 @@ async function generateMigration(dir, opts = {}) {
18751
18829
  }
18752
18830
  async function executeMigration(dir) {
18753
18831
  await getConfig();
18754
- if (!dir) {
18755
- dir = join9(process25.cwd(), "app/Models");
18756
- }
18757
18832
  const workspaceRoot = getWorkspaceRoot();
18758
18833
  const sqlDir = ensureSqlDirectory(workspaceRoot);
18759
18834
  const dialect = config5.dialect || "postgres";
@@ -18768,42 +18843,25 @@ async function executeMigration(dir) {
18768
18843
  const qb = createQueryBuilder();
18769
18844
  await createMigrationsTable(qb, dialect);
18770
18845
  const executedMigrations = await getExecutedMigrations(qb, dialect);
18771
- const permanentMigrations = [];
18772
- const transientMigrations = [];
18773
- for (const file2 of scriptFiles) {
18774
- if (file2.includes("alter-") && file2.includes("-table")) {
18775
- transientMigrations.push(file2);
18776
- } else if (!executedMigrations.includes(file2)) {
18777
- permanentMigrations.push(file2);
18778
- }
18779
- }
18780
- const totalPending = permanentMigrations.length + transientMigrations.length;
18781
- if (totalPending === 0) {
18846
+ const pending = scriptFiles.filter((file2) => !executedMigrations.includes(file2));
18847
+ if (pending.length === 0) {
18782
18848
  info("-- No pending migrations to execute");
18783
18849
  return true;
18784
18850
  }
18785
- info(`-- Executing ${totalPending} migrations (${permanentMigrations.length} permanent, ${transientMigrations.length} transient)`);
18786
- for (const file2 of permanentMigrations) {
18787
- const filePath = join9(sqlDir, file2);
18851
+ info(`-- Executing ${pending.length} migration${pending.length === 1 ? "" : "s"}`);
18852
+ for (const file2 of pending) {
18853
+ const filePath = join10(sqlDir, file2);
18788
18854
  info(`-- Executing: ${file2}`);
18789
18855
  try {
18790
18856
  await qb.file(filePath);
18791
18857
  await recordMigration(qb, file2, dialect);
18792
18858
  info(`-- \u2713 Migration ${file2} executed and recorded`);
18793
18859
  } catch (err) {
18794
- console.error(`-- \u2717 Migration ${file2} failed:`, err);
18795
- throw err;
18796
- }
18797
- }
18798
- for (const file2 of transientMigrations) {
18799
- const filePath = join9(sqlDir, file2);
18800
- info(`-- Executing: ${file2} (transient)`);
18801
- try {
18802
- await qb.file(filePath);
18803
- info(`-- \u2713 Migration ${file2} executed (not recorded)`);
18804
- unlinkSync(filePath);
18805
- info(`-- \uD83D\uDDD1\uFE0F Deleted transient migration: ${file2}`);
18806
- } catch (err) {
18860
+ if (isGeneratedMigration(sqlDir, file2) && isAlreadyAppliedError(err)) {
18861
+ await recordMigration(qb, file2, dialect);
18862
+ info(`-- \u2713 Migration ${file2} was already applied; recorded it (${err instanceof Error ? err.message : String(err)})`);
18863
+ continue;
18864
+ }
18807
18865
  console.error(`-- \u2717 Migration ${file2} failed:`, err);
18808
18866
  throw err;
18809
18867
  }
@@ -18818,7 +18876,7 @@ async function executeMigration(dir) {
18818
18876
  async function resetDatabase(dir, opts = {}) {
18819
18877
  await getConfig();
18820
18878
  if (!dir) {
18821
- dir = join9(process25.cwd(), "app/Models");
18879
+ dir = join10(process25.cwd(), "app/Models");
18822
18880
  }
18823
18881
  const dialect = opts.dialect || config5.dialect || "postgres";
18824
18882
  const driver = getDialectDriver(dialect);
@@ -18915,42 +18973,50 @@ async function resetDatabase(dir, opts = {}) {
18915
18973
  }
18916
18974
  async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
18917
18975
  if (!dir) {
18918
- dir = join9(process25.cwd(), "app/Models");
18976
+ dir = join10(process25.cwd(), "app/Models");
18919
18977
  }
18920
18978
  if (!workspaceRoot) {
18921
18979
  workspaceRoot = getWorkspaceRoot();
18922
18980
  }
18923
18981
  const dialect = String(opts.dialect || config5.dialect || "postgres");
18924
18982
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18925
- if (existsSync16(snapshotPath)) {
18983
+ if (existsSync17(snapshotPath)) {
18926
18984
  unlinkSync(snapshotPath);
18927
18985
  info(`-- Removed model snapshot file: ${snapshotPath}`);
18928
18986
  }
18929
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
18987
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18930
18988
  const statePath = String(opts.state || defaultStatePath);
18931
- if (existsSync16(statePath)) {
18989
+ if (existsSync17(statePath)) {
18932
18990
  unlinkSync(statePath);
18933
18991
  info(`-- Removed legacy migration state file: ${statePath}`);
18934
18992
  }
18935
- const sqlDir = getSqlDirectory(workspaceRoot);
18936
- if (existsSync16(sqlDir)) {
18937
- const sqlFiles = readdirSync6(sqlDir);
18938
- const migrationFiles = sqlFiles.filter((file2) => file2.endsWith(".sql"));
18939
- for (const file2 of migrationFiles) {
18940
- const filePath = join9(sqlDir, file2);
18941
- unlinkSync(filePath);
18942
- info(`-- Removed migration file: ${file2}`);
18993
+ removeGeneratedMigrationFiles(getSqlDirectory(workspaceRoot));
18994
+ }
18995
+ function removeGeneratedMigrationFiles(sqlDir) {
18996
+ if (!existsSync17(sqlDir))
18997
+ return;
18998
+ let removed = 0;
18999
+ const kept = [];
19000
+ for (const file2 of readdirSync6(sqlDir).filter((f) => f.endsWith(".sql"))) {
19001
+ if (!isGeneratedMigration(sqlDir, file2)) {
19002
+ kept.push(file2);
19003
+ continue;
18943
19004
  }
18944
- info(`-- Cleaned up ${migrationFiles.length} migration files from migrations directory`);
19005
+ unlinkSync(join10(sqlDir, file2));
19006
+ info(`-- Removed generated migration file: ${file2}`);
19007
+ removed += 1;
18945
19008
  }
19009
+ info(`-- Cleaned up ${removed} generated migration file${removed === 1 ? "" : "s"} from migrations directory`);
19010
+ if (kept.length > 0)
19011
+ info(`-- Kept ${kept.length} hand-written migration${kept.length === 1 ? "" : "s"}: ${kept.slice(0, 5).join(", ")}${kept.length > 5 ? `, +${kept.length - 5} more` : ""}`);
18946
19012
  }
18947
19013
  async function copyModelsToGenerated(_dir, _workspaceRoot) {}
18948
19014
  async function clearGeneratedDirectory(workspaceRoot) {
18949
19015
  if (!workspaceRoot) {
18950
19016
  workspaceRoot = getWorkspaceRoot();
18951
19017
  }
18952
- const generatedDir = join9(workspaceRoot, "generated");
18953
- if (existsSync16(generatedDir)) {
19018
+ const generatedDir = join10(workspaceRoot, "generated");
19019
+ if (existsSync17(generatedDir)) {
18954
19020
  try {
18955
19021
  rmSync(generatedDir, { recursive: true, force: true });
18956
19022
  info("-- Cleared generated directory");
@@ -18959,12 +19025,6 @@ async function clearGeneratedDirectory(workspaceRoot) {
18959
19025
  }
18960
19026
  }
18961
19027
  }
18962
- function getSqlDirectory(workspaceRoot) {
18963
- if (!workspaceRoot) {
18964
- workspaceRoot = getWorkspaceRoot();
18965
- }
18966
- return join9(workspaceRoot, "database", "migrations");
18967
- }
18968
19028
  async function createMigrationsTable(qb, dialect) {
18969
19029
  const driver = getDialectDriver(dialect);
18970
19030
  const createTableSql = driver.createMigrationsTable();
@@ -19003,6 +19063,7 @@ var init_migrate = __esm(() => {
19003
19063
  init_drivers();
19004
19064
  init_src2();
19005
19065
  init_introspect_db();
19066
+ init_workspace();
19006
19067
  });
19007
19068
 
19008
19069
  // src/actions/migrate-generate.ts
@@ -19014,28 +19075,49 @@ var init_migrate_generate = __esm(() => {
19014
19075
  });
19015
19076
 
19016
19077
  // src/actions/migrate-rollback.ts
19017
- import { existsSync as existsSync17, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19018
- import { dirname as dirname8, join as join10 } from "path";
19019
- import process26 from "process";
19078
+ import { existsSync as existsSync18, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19079
+ import { join as join11 } from "path";
19020
19080
  function splitSqlStatements2(sql) {
19021
19081
  const out = [];
19022
19082
  let buf = "";
19023
- let inString = false;
19024
- const lines = sql.split(`
19025
- `).filter((l) => !/^\s*--/.test(l));
19026
- const text = lines.join(`
19027
- `);
19028
- for (let i = 0;i < text.length; i++) {
19029
- const ch = text[i];
19030
- if (ch === "'")
19031
- inString = !inString;
19032
- if (ch === ";" && !inString) {
19083
+ let quote = null;
19084
+ for (let i = 0;i < sql.length; i++) {
19085
+ const ch = sql[i];
19086
+ if (quote) {
19087
+ buf += ch;
19088
+ if (ch === quote) {
19089
+ if (sql[i + 1] === quote) {
19090
+ buf += sql[i + 1];
19091
+ i++;
19092
+ } else {
19093
+ quote = null;
19094
+ }
19095
+ }
19096
+ continue;
19097
+ }
19098
+ if (ch === "'" || ch === '"' || ch === "`") {
19099
+ quote = ch;
19100
+ buf += ch;
19101
+ continue;
19102
+ }
19103
+ if (ch === "-" && sql[i + 1] === "-") {
19104
+ const newline = sql.indexOf(`
19105
+ `, i);
19106
+ i = newline === -1 ? sql.length : newline;
19107
+ continue;
19108
+ }
19109
+ if (ch === "/" && sql[i + 1] === "*") {
19110
+ const close = sql.indexOf("*/", i + 2);
19111
+ i = close === -1 ? sql.length : close + 1;
19112
+ continue;
19113
+ }
19114
+ if (ch === ";") {
19033
19115
  if (buf.trim())
19034
19116
  out.push(buf.trim());
19035
19117
  buf = "";
19036
- } else {
19037
- buf += ch;
19118
+ continue;
19038
19119
  }
19120
+ buf += ch;
19039
19121
  }
19040
19122
  if (buf.trim())
19041
19123
  out.push(buf.trim());
@@ -19049,8 +19131,20 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19049
19131
  let m;
19050
19132
  if (m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19051
19133
  down.push(`DROP TABLE IF EXISTS ${q(m[1])}`);
19052
- } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19053
- down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(m[2])}`);
19134
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+CONSTRAINT\s+["`']?(\w+)["`']?([\s\S]*)$/i.exec(stmt)) {
19135
+ if (dialect === "sqlite") {
19136
+ skipped.push(stmt);
19137
+ } else if (isMysqlLike(dialect) && /\bFOREIGN\s+KEY\b/i.test(m[3] ?? "")) {
19138
+ down.push(`ALTER TABLE ${q(m[1])} DROP FOREIGN KEY ${q(m[2])}`);
19139
+ } else {
19140
+ down.push(`ALTER TABLE ${q(m[1])} DROP CONSTRAINT ${q(m[2])}`);
19141
+ }
19142
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+["`']?(\w+)["`']?|["`'](\w+)["`']|(\w+))/i.exec(stmt)) {
19143
+ const column = m[2] ?? m[3] ?? m[4];
19144
+ if (m[4] && /^(?:PRIMARY|UNIQUE|INDEX|KEY|FOREIGN|CHECK|FULLTEXT|SPATIAL)$/i.test(m[4]))
19145
+ skipped.push(stmt);
19146
+ else
19147
+ down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(column)}`);
19054
19148
  } else if (m = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?(?:\s+ON\s+["`']?(\w+)["`']?)?/i.exec(stmt)) {
19055
19149
  down.push(isMysqlLike(dialect) && m[2] ? `DROP INDEX ${q(m[1])} ON ${q(m[2])}` : `DROP INDEX IF EXISTS ${q(m[1])}`);
19056
19150
  } else {
@@ -19059,22 +19153,6 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19059
19153
  }
19060
19154
  return { down: down.reverse(), skipped };
19061
19155
  }
19062
- function findWorkspaceRoot2(startPath) {
19063
- let currentPath = startPath;
19064
- while (currentPath !== dirname8(currentPath)) {
19065
- if (existsSync17(join10(currentPath, "package.json"))) {
19066
- return currentPath;
19067
- }
19068
- currentPath = dirname8(currentPath);
19069
- }
19070
- return process26.cwd();
19071
- }
19072
- function getSqlDirectory2(workspaceRoot) {
19073
- if (!workspaceRoot) {
19074
- workspaceRoot = findWorkspaceRoot2(process26.cwd());
19075
- }
19076
- return join10(workspaceRoot, "database", "migrations");
19077
- }
19078
19156
  async function migrateRollback(options = {}) {
19079
19157
  const steps = options.steps || 1;
19080
19158
  const reverseSchema = options.reverseSchema !== false;
@@ -19107,12 +19185,12 @@ async function migrateRollback(options = {}) {
19107
19185
  console.log(` - ${migration.migration}`);
19108
19186
  }
19109
19187
  console.log();
19110
- const sqlDir = getSqlDirectory2();
19188
+ const sqlDir = getSqlDirectory();
19111
19189
  let reversedAny = false;
19112
19190
  for (const migration of migrationsToRollback) {
19113
19191
  try {
19114
- const filePath = join10(sqlDir, migration.migration);
19115
- if (reverseSchema && existsSync17(filePath)) {
19192
+ const filePath = join11(sqlDir, migration.migration);
19193
+ if (reverseSchema && existsSync18(filePath)) {
19116
19194
  const forwardSql = readFileSync4(filePath, "utf8");
19117
19195
  const { down, skipped } = deriveDownStatements(forwardSql);
19118
19196
  for (const stmt of down) {
@@ -19123,10 +19201,10 @@ async function migrateRollback(options = {}) {
19123
19201
  if (skipped.length > 0)
19124
19202
  console.log(`-- \u26A0\uFE0F ${skipped.length} statement(s) in ${migration.migration} could not be auto-reversed (data/complex DDL) \u2014 reverse manually.`);
19125
19203
  }
19126
- const deleteSql = `DELETE FROM migrations WHERE migration = $1`;
19204
+ const deleteSql = `DELETE FROM migrations WHERE migration = ${getPlaceholder(1)}`;
19127
19205
  await qb.unsafe(deleteSql, [migration.migration]);
19128
19206
  console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
19129
- if (existsSync17(filePath)) {
19207
+ if (options.deleteFiles && existsSync18(filePath)) {
19130
19208
  unlinkSync2(filePath);
19131
19209
  console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
19132
19210
  }
@@ -19149,37 +19227,20 @@ async function migrateRollback(options = {}) {
19149
19227
  var init_migrate_rollback = __esm(() => {
19150
19228
  init_config();
19151
19229
  init_src2();
19230
+ init_workspace();
19152
19231
  });
19153
19232
 
19154
19233
  // src/actions/migrate-status.ts
19155
- import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
19156
- import { dirname as dirname9, join as join11 } from "path";
19157
- import process27 from "process";
19158
- function findWorkspaceRoot3(startPath) {
19159
- let currentPath = startPath;
19160
- while (currentPath !== dirname9(currentPath)) {
19161
- if (existsSync18(join11(currentPath, "package.json"))) {
19162
- return currentPath;
19163
- }
19164
- currentPath = dirname9(currentPath);
19165
- }
19166
- return process27.cwd();
19167
- }
19168
- function getSqlDirectory3(workspaceRoot) {
19169
- if (!workspaceRoot) {
19170
- workspaceRoot = findWorkspaceRoot3(process27.cwd());
19171
- }
19172
- return join11(workspaceRoot, "database", "migrations");
19173
- }
19234
+ import { existsSync as existsSync19, readdirSync as readdirSync8 } from "fs";
19174
19235
  async function migrateStatus() {
19175
19236
  const dialect = config5.dialect || "postgres";
19176
19237
  const driver = getDialectDriver(dialect);
19177
- const sqlDir = getSqlDirectory3();
19238
+ const sqlDir = getSqlDirectory();
19178
19239
  console.log("-- Migration Status");
19179
19240
  console.log(`-- Dialect: ${dialect}`);
19180
19241
  console.log(`-- SQL directory: ${sqlDir}`);
19181
19242
  console.log();
19182
- if (!existsSync18(sqlDir)) {
19243
+ if (!existsSync19(sqlDir)) {
19183
19244
  console.log("-- No SQL directory found. No migrations have been created yet.");
19184
19245
  return [];
19185
19246
  }
@@ -19200,27 +19261,16 @@ async function migrateStatus() {
19200
19261
  console.log();
19201
19262
  }
19202
19263
  const executedMap = new Map(executedMigrations.map((m) => [m.migration, m.executed_at]));
19203
- const statuses = migrationFiles.map((file2) => {
19204
- if (file2.includes("alter-") && file2.includes("-table")) {
19205
- return {
19206
- file: file2,
19207
- status: "transient"
19208
- };
19209
- }
19210
- const executedAt = executedMap.get(file2);
19211
- return {
19212
- file: file2,
19213
- status: executedAt ? "executed" : "pending",
19214
- executedAt
19215
- };
19216
- });
19264
+ const statuses = migrationFiles.map((file2) => ({
19265
+ file: file2,
19266
+ status: executedMap.has(file2) ? "executed" : "pending",
19267
+ executedAt: executedMap.get(file2)
19268
+ }));
19217
19269
  const executed = statuses.filter((s) => s.status === "executed");
19218
19270
  const pending = statuses.filter((s) => s.status === "pending");
19219
- const transient = statuses.filter((s) => s.status === "transient");
19220
19271
  console.log(`-- Total migrations: ${migrationFiles.length}`);
19221
19272
  console.log(`-- Executed: ${executed.length}`);
19222
19273
  console.log(`-- Pending: ${pending.length}`);
19223
- console.log(`-- Transient: ${transient.length}`);
19224
19274
  console.log();
19225
19275
  if (executed.length > 0) {
19226
19276
  console.log("\u2713 Executed Migrations:");
@@ -19236,13 +19286,6 @@ async function migrateStatus() {
19236
19286
  }
19237
19287
  console.log();
19238
19288
  }
19239
- if (transient.length > 0) {
19240
- console.log("\u26A1 Transient Migrations (ALTER TABLE - not tracked):");
19241
- for (const migration of transient) {
19242
- console.log(` - ${migration.file}`);
19243
- }
19244
- console.log();
19245
- }
19246
19289
  return statuses;
19247
19290
  } catch (err) {
19248
19291
  console.error("-- Failed to get migration status:", err);
@@ -19256,14 +19299,15 @@ var init_migrate_status = __esm(() => {
19256
19299
  init_config();
19257
19300
  init_drivers();
19258
19301
  init_src2();
19302
+ init_workspace();
19259
19303
  });
19260
19304
 
19261
19305
  // src/actions/model-show.ts
19262
19306
  import { readdirSync as readdirSync9 } from "fs";
19263
19307
  import { extname as extname2, join as join12 } from "path";
19264
- import process28 from "process";
19308
+ import process26 from "process";
19265
19309
  async function modelShow(modelName, options = {}) {
19266
- const dir = options.dir || join12(process28.cwd(), "app/Models");
19310
+ const dir = options.dir || join12(process26.cwd(), "app/Models");
19267
19311
  try {
19268
19312
  const files = readdirSync9(dir);
19269
19313
  const modelFile = files.find((f) => {
@@ -19477,9 +19521,9 @@ var init_query_explain_all = __esm(() => {
19477
19521
  // src/actions/relation-diagram.ts
19478
19522
  import { writeFileSync as writeFileSync12 } from "fs";
19479
19523
  import { join as join14 } from "path";
19480
- import process29 from "process";
19524
+ import process27 from "process";
19481
19525
  async function relationDiagram(options = {}) {
19482
- const dir = options.dir || join14(process29.cwd(), "app/Models");
19526
+ const dir = options.dir || join14(process27.cwd(), "app/Models");
19483
19527
  const format = options.format || "mermaid";
19484
19528
  try {
19485
19529
  const models = await loadModels({ modelsDir: dir });
@@ -19599,25 +19643,25 @@ var init_relation_diagram = __esm(() => {
19599
19643
  });
19600
19644
 
19601
19645
  // src/actions/seed.ts
19602
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19646
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19603
19647
  import { dirname as dirname10, join as join15 } from "path";
19604
- import process30 from "process";
19605
- function findWorkspaceRoot4(startPath) {
19648
+ import process28 from "process";
19649
+ function findWorkspaceRoot3(startPath) {
19606
19650
  let currentPath = startPath;
19607
19651
  while (currentPath !== dirname10(currentPath)) {
19608
- if (existsSync19(join15(currentPath, "package.json"))) {
19652
+ if (existsSync20(join15(currentPath, "package.json"))) {
19609
19653
  return currentPath;
19610
19654
  }
19611
19655
  currentPath = dirname10(currentPath);
19612
19656
  }
19613
- return process30.cwd();
19657
+ return process28.cwd();
19614
19658
  }
19615
19659
  async function loadSeeders(seedersDir) {
19616
- if (!existsSync19(seedersDir)) {
19660
+ if (!existsSync20(seedersDir)) {
19617
19661
  console.log(`-- Seeders directory not found: ${seedersDir}`);
19618
19662
  return [];
19619
19663
  }
19620
- const files = readdirSync11(seedersDir);
19664
+ const files = readdirSync11(seedersDir).sort((a, b) => a.localeCompare(b));
19621
19665
  const seederFiles = files.filter((file2) => (file2.endsWith(".ts") || file2.endsWith(".js")) && file2 !== "index.ts" && file2 !== "index.js");
19622
19666
  const seeders = [];
19623
19667
  for (const file2 of seederFiles) {
@@ -19630,6 +19674,7 @@ async function loadSeeders(seedersDir) {
19630
19674
  if (instance && typeof instance.run === "function") {
19631
19675
  seeders.push({
19632
19676
  name: file2.replace(/\.(ts|js)$/, ""),
19677
+ className: String(SeederClass.name || ""),
19633
19678
  instance
19634
19679
  });
19635
19680
  }
@@ -19642,7 +19687,7 @@ async function loadSeeders(seedersDir) {
19642
19687
  return seeders;
19643
19688
  }
19644
19689
  async function runSeeders(config6 = {}) {
19645
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19690
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19646
19691
  const seedersDir = config6.seedersDir || join15(workspaceRoot, "database/seeders");
19647
19692
  const verbose = config6.verbose ?? true;
19648
19693
  if (verbose) {
@@ -19677,17 +19722,18 @@ async function runSeeders(config6 = {}) {
19677
19722
  }
19678
19723
  }
19679
19724
  async function runSeeder(className, options = {}) {
19680
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19725
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19681
19726
  const seedersDir = join15(workspaceRoot, "database/seeders");
19682
19727
  const verbose = options.verbose ?? true;
19683
19728
  if (verbose) {
19684
19729
  console.log(`-- Running seeder: ${className}`);
19685
19730
  }
19686
19731
  const seeders = await loadSeeders(seedersDir);
19687
- const seeder = seeders.find((s) => s.name === className);
19732
+ const seeder = seeders.find((s) => s.className === className) ?? seeders.find((s) => s.name === className);
19688
19733
  if (!seeder) {
19689
- console.error(`-- Seeder not found: ${className}`);
19690
- throw new Error(`Seeder not found: ${className}`);
19734
+ const known = seeders.map((s) => s.className || s.name).join(", ") || "none";
19735
+ console.error(`-- Seeder not found: ${className}. Available: ${known}`);
19736
+ throw new Error(`Seeder not found: ${className}. Available: ${known}`);
19691
19737
  }
19692
19738
  const qb = createQueryBuilder();
19693
19739
  try {
@@ -19701,9 +19747,9 @@ async function runSeeder(className, options = {}) {
19701
19747
  }
19702
19748
  }
19703
19749
  async function makeSeeder(name) {
19704
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19750
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19705
19751
  const seedersDir = join15(workspaceRoot, "database/seeders");
19706
- if (!existsSync19(seedersDir)) {
19752
+ if (!existsSync20(seedersDir)) {
19707
19753
  mkdirSync9(seedersDir, { recursive: true });
19708
19754
  console.log(`-- Created seeders directory: ${seedersDir}`);
19709
19755
  }
@@ -19711,7 +19757,7 @@ async function makeSeeder(name) {
19711
19757
  const className = `${baseName}Seeder`;
19712
19758
  const fileName = `${className}.ts`;
19713
19759
  const filePath = join15(seedersDir, fileName);
19714
- if (existsSync19(filePath)) {
19760
+ if (existsSync20(filePath)) {
19715
19761
  console.error(`-- Seeder already exists: ${filePath}`);
19716
19762
  throw new Error(`Seeder already exists: ${filePath}`);
19717
19763
  }
@@ -19765,7 +19811,7 @@ export default class ${className} extends Seeder {
19765
19811
  console.log(`-- \u2713 Created seeder: ${filePath}`);
19766
19812
  }
19767
19813
  async function freshDatabase(options = {}) {
19768
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19814
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19769
19815
  const modelsDir = options.modelsDir || join15(workspaceRoot, "app/Models");
19770
19816
  const seedersDir = options.seedersDir || join15(workspaceRoot, "database/seeders");
19771
19817
  const verbose = options.verbose ?? true;
@@ -19825,22 +19871,22 @@ var init_unsafe = __esm(() => {
19825
19871
  });
19826
19872
 
19827
19873
  // src/actions/validate.ts
19828
- import { existsSync as existsSync20 } from "fs";
19874
+ import { existsSync as existsSync21 } from "fs";
19829
19875
  import { dirname as dirname11, join as join16 } from "path";
19830
- import process31 from "process";
19831
- function findWorkspaceRoot5(startPath) {
19876
+ import process29 from "process";
19877
+ function findWorkspaceRoot4(startPath) {
19832
19878
  let currentPath = startPath;
19833
19879
  while (currentPath !== dirname11(currentPath)) {
19834
- if (existsSync20(join16(currentPath, "package.json"))) {
19880
+ if (existsSync21(join16(currentPath, "package.json"))) {
19835
19881
  return currentPath;
19836
19882
  }
19837
19883
  currentPath = dirname11(currentPath);
19838
19884
  }
19839
- return process31.cwd();
19885
+ return process29.cwd();
19840
19886
  }
19841
19887
  async function validateSchema(dir) {
19842
19888
  if (!dir) {
19843
- dir = join16(findWorkspaceRoot5(process31.cwd()), "app/Models");
19889
+ dir = join16(findWorkspaceRoot4(process29.cwd()), "app/Models");
19844
19890
  }
19845
19891
  const dialect = config5.dialect || "postgres";
19846
19892
  console.log("-- Validating Schema");
@@ -25091,18 +25137,11 @@ function toSnakeCase(str) {
25091
25137
  snakeCaseCache.set(str, cached);
25092
25138
  return cached;
25093
25139
  }
25094
- function toTableName(modelName) {
25140
+ function toTableName2(modelName) {
25095
25141
  let cached = tableNameCache.get(modelName);
25096
25142
  if (cached !== undefined)
25097
25143
  return cached;
25098
- const snake = toSnakeCase(modelName);
25099
- if (snake.endsWith("y") && !snake.endsWith("ay") && !snake.endsWith("ey") && !snake.endsWith("oy") && !snake.endsWith("uy")) {
25100
- cached = snake.slice(0, -1) + "ies";
25101
- } else if (snake.endsWith("s") || snake.endsWith("x") || snake.endsWith("ch") || snake.endsWith("sh")) {
25102
- cached = `${snake}es`;
25103
- } else {
25104
- cached = `${snake}s`;
25105
- }
25144
+ cached = toTableName(modelName);
25106
25145
  tableNameCache.set(modelName, cached);
25107
25146
  return cached;
25108
25147
  }
@@ -25187,14 +25226,14 @@ function resolveRelation(definition, relationName) {
25187
25226
  const hasManyModel = findModelName(definition.hasMany);
25188
25227
  if (hasManyModel) {
25189
25228
  const relatedModel = getModelFromRegistry(hasManyModel);
25190
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasManyModel);
25229
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasManyModel);
25191
25230
  const foreignKey = toSnakeCase(parentName) + "_id";
25192
25231
  return { type: "hasMany", relatedModelName: hasManyModel, relatedTable, foreignKey, localKey: parentPk };
25193
25232
  }
25194
25233
  const hasOneModel = findModelName(definition.hasOne);
25195
25234
  if (hasOneModel) {
25196
25235
  const relatedModel = getModelFromRegistry(hasOneModel);
25197
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasOneModel);
25236
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasOneModel);
25198
25237
  const foreignKey = toSnakeCase(parentName) + "_id";
25199
25238
  return { type: "hasOne", relatedModelName: hasOneModel, relatedTable, foreignKey, localKey: parentPk };
25200
25239
  }
@@ -25202,7 +25241,7 @@ function resolveRelation(definition, relationName) {
25202
25241
  if (belongsToEntry) {
25203
25242
  const belongsToModel = belongsToEntry.model;
25204
25243
  const relatedModel = getModelFromRegistry(belongsToModel);
25205
- const relatedTable = relatedModel?.getTable?.() || toTableName(belongsToModel);
25244
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(belongsToModel);
25206
25245
  const relatedPk = relatedModel?.getDefinition?.()?.primaryKey || "id";
25207
25246
  const foreignKey = belongsToEntry.foreignKey || `${toSnakeCase(belongsToModel)}_id`;
25208
25247
  return { type: "belongsTo", relatedModelName: belongsToModel, relatedTable, foreignKey, localKey: relatedPk };
@@ -25212,7 +25251,7 @@ function resolveRelation(definition, relationName) {
25212
25251
  const isConfig = typeof found.entry === "object";
25213
25252
  const relatedModelName = isConfig ? found.entry.model : found.entry;
25214
25253
  const relatedModel = getModelFromRegistry(relatedModelName);
25215
- const relatedTable = relatedModel?.getTable?.() || toTableName(relatedModelName);
25254
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(relatedModelName);
25216
25255
  const config6 = isConfig ? found.entry : null;
25217
25256
  let pivotTable;
25218
25257
  let pivotModelName;
@@ -25224,7 +25263,7 @@ function resolveRelation(definition, relationName) {
25224
25263
  if (!throughModel) {
25225
25264
  throw new Error(`[orm] belongsToMany relation '${relationName}' on '${parentName}' references unknown through model '${config6.through}'. Make sure '${config6.through}' is registered.`);
25226
25265
  }
25227
- pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName(config6.through);
25266
+ pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName2(config6.through);
25228
25267
  } else {
25229
25268
  const a2 = singularizeWord(parentTable);
25230
25269
  const b2 = singularizeWord(relatedTable);
@@ -25275,10 +25314,10 @@ function resolveRelation(definition, relationName) {
25275
25314
  return null;
25276
25315
  const throughModel = getModelFromRegistry(throughModelName);
25277
25316
  const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
25278
- const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
25317
+ const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName2(throughModelName);
25279
25318
  const throughPk = throughDef?.primaryKey || "id";
25280
25319
  const targetModel = getModelFromRegistry(targetModelName);
25281
- const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
25320
+ const targetTable = targetModel?.getTable?.() || toTableName2(targetModelName);
25282
25321
  return {
25283
25322
  type,
25284
25323
  relatedModelName: targetModelName,
@@ -27875,7 +27914,7 @@ function buildDatabaseSchema(models) {
27875
27914
  for (const modelName of Object.keys(models)) {
27876
27915
  const rawModel = models[modelName];
27877
27916
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27878
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
27917
+ const table = tableNameFor(m2);
27879
27918
  const attrs = m2.attributes ?? {};
27880
27919
  const columns = {};
27881
27920
  for (const key of Object.keys(attrs))
@@ -27887,30 +27926,53 @@ function buildDatabaseSchema(models) {
27887
27926
  }
27888
27927
  return schema;
27889
27928
  }
27929
+ var init_factory = () => {};
27890
27930
 
27891
27931
  // src/loader.ts
27892
- import { readdirSync as readdirSync12, statSync as statSync5 } from "fs";
27893
- import { basename, extname as extname4 } from "path";
27894
- import process33 from "process";
27895
- async function loadModels(options) {
27896
- const cwd = options.cwd ?? process33.cwd();
27897
- const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
27898
- const result = {};
27899
- const entries = readdirSync12(dir);
27932
+ import { readdirSync as readdirSync12 } from "fs";
27933
+ import { basename, extname as extname4, isAbsolute as isAbsolute3, join as join17 } from "path";
27934
+ import process30 from "process";
27935
+ function isModelFile(name) {
27936
+ const ext = extname4(name);
27937
+ if (!MODEL_EXTENSIONS.has(ext))
27938
+ return false;
27939
+ const base = basename(name, ext);
27940
+ if (base.startsWith("_") || base === "index")
27941
+ return false;
27942
+ return !base.endsWith(".d");
27943
+ }
27944
+ function isModelDefinition(def) {
27945
+ if (!def || typeof def !== "object")
27946
+ return false;
27947
+ const d2 = def;
27948
+ return typeof d2.name === "string" || typeof d2.table === "string";
27949
+ }
27950
+ function collectModelFiles(dir) {
27951
+ const files = [];
27952
+ const entries = readdirSync12(dir, { withFileTypes: true }).sort((a2, b2) => a2.name.localeCompare(b2.name));
27900
27953
  for (const entry of entries) {
27901
- const full = `${dir}/${entry}`;
27902
- const st = statSync5(full);
27903
- if (st.isDirectory())
27904
- continue;
27905
- const ext = extname4(full);
27906
- if (![".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"].includes(ext))
27954
+ const full = join17(dir, entry.name);
27955
+ if (entry.isDirectory()) {
27956
+ files.push(...collectModelFiles(full));
27907
27957
  continue;
27958
+ }
27959
+ if (isModelFile(entry.name))
27960
+ files.push(full);
27961
+ }
27962
+ return files;
27963
+ }
27964
+ async function loadModels(options) {
27965
+ const cwd = options.cwd ?? process30.cwd();
27966
+ const dir = isAbsolute3(options.modelsDir) ? options.modelsDir : join17(cwd, options.modelsDir);
27967
+ const result = {};
27968
+ for (const full of collectModelFiles(dir)) {
27908
27969
  const cacheBuster = `?t=${Date.now()}-${Math.random().toString(36).slice(2)}`;
27909
27970
  const mod = await import(`${full}${cacheBuster}`);
27910
27971
  const rawDef = mod.default ?? mod;
27911
27972
  const def = rawDef.definition ?? rawDef.getDefinition?.() ?? rawDef;
27912
- const fileName = basename(entry, ext);
27913
- const name = def.name ?? fileName;
27973
+ if (!isModelDefinition(def))
27974
+ continue;
27975
+ const name = def.name ?? basename(full, extname4(full));
27914
27976
  result[name] = {
27915
27977
  ...def,
27916
27978
  name
@@ -27918,7 +27980,10 @@ async function loadModels(options) {
27918
27980
  }
27919
27981
  return result;
27920
27982
  }
27921
- var init_loader = () => {};
27983
+ var MODEL_EXTENSIONS;
27984
+ var init_loader = __esm(() => {
27985
+ MODEL_EXTENSIONS = new Set([".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]);
27986
+ });
27922
27987
 
27923
27988
  // src/meta.ts
27924
27989
  function buildSchemaMeta(models) {
@@ -27930,7 +27995,7 @@ function buildSchemaMeta(models) {
27930
27995
  for (const name of Object.keys(models)) {
27931
27996
  const rawModel = models[name];
27932
27997
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27933
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
27998
+ const table = tableNameFor(m2);
27934
27999
  modelToTable[name] = table;
27935
28000
  tableToModel[table] = name;
27936
28001
  primaryKeys[table] = m2.primaryKey ?? "id";
@@ -28004,9 +28069,9 @@ function buildSchemaMeta(models) {
28004
28069
  var init_meta = () => {};
28005
28070
 
28006
28071
  // src/migrations.ts
28007
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync14 } from "fs";
28008
- import { dirname as dirname12, join as join17 } from "path";
28009
- import process35 from "process";
28072
+ import { existsSync as existsSync23, mkdirSync as mkdirSync10, readdirSync as readdirSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync14 } from "fs";
28073
+ import { dirname as dirname12, join as join18 } from "path";
28074
+ import process31 from "process";
28010
28075
  function info2(message) {
28011
28076
  if (config5.verbose)
28012
28077
  console.log(message);
@@ -28017,36 +28082,80 @@ function snakeCase(str) {
28017
28082
  function singularizeTable(table) {
28018
28083
  return singularizerFor(config5.relations?.singularizeStrategy)(table);
28019
28084
  }
28020
- function findWorkspaceRoot6(startPath) {
28085
+ function lastCreatedMigrationFiles() {
28086
+ return [...createdMigrationFiles];
28087
+ }
28088
+ function findWorkspaceRoot5(startPath) {
28021
28089
  let currentPath = startPath;
28022
28090
  while (currentPath !== dirname12(currentPath)) {
28023
- if (existsSync21(join17(currentPath, "package.json"))) {
28091
+ if (existsSync23(join18(currentPath, "package.json"))) {
28024
28092
  return currentPath;
28025
28093
  }
28026
28094
  currentPath = dirname12(currentPath);
28027
28095
  }
28028
- return process35.cwd();
28096
+ return process31.cwd();
28029
28097
  }
28030
28098
  function ensureSqlDirectory2() {
28031
- const workspaceRoot = findWorkspaceRoot6(process35.cwd());
28032
- const sqlDir = join17(workspaceRoot, "database", "migrations");
28033
- if (!existsSync21(sqlDir)) {
28099
+ const workspaceRoot = findWorkspaceRoot5(process31.cwd());
28100
+ const sqlDir = join18(workspaceRoot, "database", "migrations");
28101
+ if (!existsSync23(sqlDir)) {
28034
28102
  mkdirSync10(sqlDir, { recursive: true });
28035
28103
  info2(`-- Created SQL directory: ${sqlDir}`);
28036
28104
  }
28037
28105
  return sqlDir;
28038
28106
  }
28107
+ function isGeneratedMigrationSql(sql2) {
28108
+ return sql2.trimStart().startsWith(GENERATED_MARKER);
28109
+ }
28110
+ function migrationBody(sql2) {
28111
+ return sql2.split(`
28112
+ `).filter((line) => !line.trimStart().startsWith("--")).join(`
28113
+ `).replace(/\s+/g, " ").trim().replace(/;+$/, "");
28114
+ }
28115
+ function existingMigrationBodies(sqlDir) {
28116
+ const bodies = new Set;
28117
+ let files;
28118
+ try {
28119
+ files = readdirSync13(sqlDir).filter((f2) => f2.endsWith(".sql"));
28120
+ } catch {
28121
+ return bodies;
28122
+ }
28123
+ for (const file2 of files) {
28124
+ try {
28125
+ bodies.add(migrationBody(readFileSync6(join18(sqlDir, file2), "utf8")));
28126
+ } catch {}
28127
+ }
28128
+ return bodies;
28129
+ }
28039
28130
  function createMigrationFile(statement, fileName) {
28040
28131
  if (!statement)
28041
28132
  return false;
28042
28133
  const sqlDir = ensureSqlDirectory2();
28134
+ if (existingCorpusBodies === null)
28135
+ existingCorpusBodies = existingMigrationBodies(sqlDir);
28136
+ const body = migrationBody(statement);
28137
+ if (body.length > 0 && existingCorpusBodies.has(body)) {
28138
+ info2(`-- Migration already in the corpus, not writing it again: ${fileName}`);
28139
+ return false;
28140
+ }
28043
28141
  migrationCounter++;
28044
- const sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28045
- const fullFileName = `${sequence}-${fileName}.sql`;
28046
- const filePath = join17(sqlDir, fullFileName);
28047
- writeFileSync14(filePath, statement);
28142
+ let sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28143
+ let fullFileName = `${sequence}-${fileName}.sql`;
28144
+ if (!useDeterministicNames) {
28145
+ let next = Number(sequence);
28146
+ while (existsSync23(join18(sqlDir, fullFileName))) {
28147
+ next += 1;
28148
+ sequence = String(next);
28149
+ fullFileName = `${sequence}-${fileName}.sql`;
28150
+ }
28151
+ }
28152
+ const filePath = join18(sqlDir, fullFileName);
28153
+ writeFileSync14(filePath, `${GENERATED_MARKER}
28154
+ ${statement}`);
28048
28155
  info2(`-- Migration file created: ${fullFileName}`);
28049
28156
  migrationsCreatedCount++;
28157
+ createdMigrationFiles.push(fullFileName);
28158
+ existingCorpusBodies?.add(body);
28050
28159
  return true;
28051
28160
  }
28052
28161
  function guessTypeFromName(columnName) {
@@ -28221,7 +28330,7 @@ function buildMigrationPlan2(models, options) {
28221
28330
  const tables = [];
28222
28331
  for (const modelName of Object.keys(models)) {
28223
28332
  const model = models[modelName];
28224
- const table = model.table || `${String(model.name).toLowerCase()}s`;
28333
+ const table = tableNameFor(model);
28225
28334
  const primaryKey = model.primaryKey ?? "id";
28226
28335
  const _autoIncrement = model.autoIncrement !== false;
28227
28336
  const attrs = model.attributes ?? {};
@@ -28313,9 +28422,7 @@ function buildMigrationPlan2(models, options) {
28313
28422
  if (columns.some((c2) => c2.name === fkColumnName))
28314
28423
  continue;
28315
28424
  const refTable = meta.modelToTable[rel.model];
28316
- if (!refTable)
28317
- continue;
28318
- const refPk = meta.primaryKeys[refTable] ?? "id";
28425
+ const refPk = refTable ? meta.primaryKeys[refTable] ?? "id" : undefined;
28319
28426
  columns.push({
28320
28427
  name: fkColumnName,
28321
28428
  type: "bigint",
@@ -28323,7 +28430,7 @@ function buildMigrationPlan2(models, options) {
28323
28430
  isUnique: false,
28324
28431
  isNullable: true,
28325
28432
  hasDefault: false,
28326
- references: { table: refTable, column: refPk, onDelete: rel.onDelete }
28433
+ references: refTable && refPk ? { table: refTable, column: refPk, onDelete: rel.onDelete } : undefined
28327
28434
  });
28328
28435
  }
28329
28436
  const traits = model.traits;
@@ -28404,7 +28511,7 @@ function buildMigrationPlan2(models, options) {
28404
28511
  for (const modelName of Object.keys(models)) {
28405
28512
  const model = models[modelName];
28406
28513
  const traits = model?.traits ?? {};
28407
- const modelTable = model.table || `${String(model.name).toLowerCase()}s`;
28514
+ const modelTable = tableNameFor(model);
28408
28515
  const singular = singularizeTable(modelTable);
28409
28516
  if (traits.likeable) {
28410
28517
  const opts = typeof traits.likeable === "object" ? traits.likeable : {};
@@ -28471,7 +28578,7 @@ function buildMigrationPlan2(models, options) {
28471
28578
  const btm = model?.belongsToMany;
28472
28579
  if (!btm || typeof btm !== "object" || Array.isArray(btm))
28473
28580
  continue;
28474
- const parentTable = model.table || `${String(model.name).toLowerCase()}s`;
28581
+ const parentTable = tableNameFor(model);
28475
28582
  const parentSingular = singularizeTable(parentTable);
28476
28583
  for (const [relKey, value] of Object.entries(btm)) {
28477
28584
  const cfg = value;
@@ -28532,6 +28639,8 @@ function generateSql(plan, opts = {}) {
28532
28639
  migrationCounter = 0;
28533
28640
  migrationsCreatedCount = 0;
28534
28641
  migrationsUpdatedCount = 0;
28642
+ createdMigrationFiles = [];
28643
+ existingCorpusBodies = null;
28535
28644
  useDeterministicNames = true;
28536
28645
  const emit = opts.dryRun ? () => false : createMigrationFile;
28537
28646
  const statements = [];
@@ -28793,12 +28902,13 @@ function referencesAreDifferent(r1, r2) {
28793
28902
  const act = (a2) => (a2 ?? "no action").toLowerCase();
28794
28903
  return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
28795
28904
  }
28905
+ function indexKey(index) {
28906
+ return `${index.type}:${index.name}:${index.columns.join(",")}`;
28907
+ }
28796
28908
  function mapIndexesByKey(indexes) {
28797
28909
  const map = {};
28798
- for (const i2 of indexes) {
28799
- const key = `${i2.type}:${i2.name}:${i2.columns.join(",")}`;
28800
- map[key] = i2;
28801
- }
28910
+ for (const i2 of indexes)
28911
+ map[indexKey(i2)] = i2;
28802
28912
  return map;
28803
28913
  }
28804
28914
  function generateDiffOperations(previous, next, opts = {}) {
@@ -28813,10 +28923,13 @@ function generateDiffOperations(previous, next, opts = {}) {
28813
28923
  return { statements: statements2, operations: operations2 };
28814
28924
  }
28815
28925
  const applyRenames = opts.applyRenames !== false;
28926
+ const preserveUnknownColumns = opts.preserveUnknownColumns === true;
28816
28927
  const emit = opts.dryRun ? () => false : createMigrationFile;
28817
28928
  migrationCounter = 0;
28818
28929
  migrationsCreatedCount = 0;
28819
28930
  migrationsUpdatedCount = 0;
28931
+ createdMigrationFiles = [];
28932
+ existingCorpusBodies = null;
28820
28933
  useDeterministicNames = false;
28821
28934
  const chunks = [];
28822
28935
  const operations = [];
@@ -28915,22 +29028,32 @@ function generateDiffOperations(previous, next, opts = {}) {
28915
29028
  }
28916
29029
  for (const tableName of Object.keys(nextTables)) {
28917
29030
  const prev = prevTables[tableName];
28918
- const curr = nextTables[tableName];
29031
+ let curr = nextTables[tableName];
28919
29032
  if (!prev)
28920
29033
  continue;
28921
29034
  const prevCols = mapColumnsByName(prev.columns);
28922
- const currCols = mapColumnsByName(curr.columns);
28923
29035
  const prevIdx = mapIndexesByKey(prev.indexes);
28924
- const currIdx = mapIndexesByKey(curr.indexes);
29036
+ let currCols = mapColumnsByName(curr.columns);
29037
+ let currIdx = mapIndexesByKey(curr.indexes);
28925
29038
  let renames = [];
28926
29039
  let removedCols = Object.keys(prevCols).filter((n2) => !currCols[n2]);
28927
29040
  let addedCols = Object.keys(currCols).filter((n2) => !prevCols[n2]);
28928
- if (applyRenames) {
29041
+ if (applyRenames && !preserveUnknownColumns) {
28929
29042
  const detected = detectColumnRenames(prevCols, currCols, dialect);
28930
29043
  renames = detected.renames;
28931
29044
  removedCols = detected.removed;
28932
29045
  addedCols = detected.added;
28933
29046
  }
29047
+ if (preserveUnknownColumns && removedCols.length > 0) {
29048
+ const preserved = removedCols.map((name) => prevCols[name]);
29049
+ const preservedNames = new Set(removedCols);
29050
+ const keptIndexes = prev.indexes.filter((idx) => idx.columns.some((c2) => preservedNames.has(c2)) && !currIdx[indexKey(idx)]);
29051
+ info2(`-- Keeping ${preserved.length} column(s) on "${curr.table}" no model declares: ${removedCols.join(", ")} \u2014 reconciled from the database, where an undeclared column is out of scope rather than removed`);
29052
+ curr = { ...curr, columns: [...curr.columns, ...preserved], indexes: [...curr.indexes, ...keptIndexes] };
29053
+ currCols = mapColumnsByName(curr.columns);
29054
+ currIdx = mapIndexesByKey(curr.indexes);
29055
+ removedCols = [];
29056
+ }
28934
29057
  const modifiedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && columnsAreDifferent(prevCols[n2], currCols[n2], dialect));
28935
29058
  const fkChangedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && referencesAreDifferent(prevCols[n2].references, currCols[n2].references));
28936
29059
  if (dialect === "sqlite") {
@@ -29078,11 +29201,12 @@ function generateDiffOperations(previous, next, opts = {}) {
29078
29201
  function generateDiffSql(previous, next, opts = {}) {
29079
29202
  return generateDiffOperations(previous, next, opts).statements;
29080
29203
  }
29081
- var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true;
29204
+ var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true, createdMigrationFiles, existingCorpusBodies = null, GENERATED_MARKER = "-- qb:generated";
29082
29205
  var init_migrations = __esm(() => {
29083
29206
  init_config();
29084
29207
  init_drivers();
29085
29208
  init_meta();
29209
+ createdMigrationFiles = [];
29086
29210
  });
29087
29211
 
29088
29212
  // src/schema.ts
@@ -29116,6 +29240,7 @@ var init_src2 = __esm(() => {
29116
29240
  init_dynamodb_single_table();
29117
29241
  init_dynamodb_tooling_adapter();
29118
29242
  init_dynamodb();
29243
+ init_factory();
29119
29244
  init_loader();
29120
29245
  init_meta();
29121
29246
  init_migrations();
@@ -29125,11 +29250,11 @@ var init_src2 = __esm(() => {
29125
29250
  });
29126
29251
 
29127
29252
  // ../../node_modules/@stacksjs/clapp/dist/index.js
29128
- import { existsSync as existsSync23 } from "fs";
29253
+ import { existsSync as existsSync25 } from "fs";
29129
29254
  import fs from "fs/promises";
29130
29255
  import os from "os";
29131
29256
  import path from "path";
29132
- import process36 from "process";
29257
+ import process33 from "process";
29133
29258
  import { EventEmitter } from "events";
29134
29259
  import process52 from "process";
29135
29260
  import process210 from "process";
@@ -29153,10 +29278,10 @@ class Telemetry {
29153
29278
  this.configPath = path.join(configDir, "telemetry.json");
29154
29279
  }
29155
29280
  async isEnabled() {
29156
- if (process36.env.DO_NOT_TRACK === "1" || process36.env.DO_NOT_TRACK === "true") {
29281
+ if (process33.env.DO_NOT_TRACK === "1" || process33.env.DO_NOT_TRACK === "true") {
29157
29282
  return false;
29158
29283
  }
29159
- if (process36.env.NO_TELEMETRY === "1" || process36.env.NO_TELEMETRY === "true") {
29284
+ if (process33.env.NO_TELEMETRY === "1" || process33.env.NO_TELEMETRY === "true") {
29160
29285
  return false;
29161
29286
  }
29162
29287
  const config6 = await this.loadConfig();
@@ -29185,7 +29310,7 @@ class Telemetry {
29185
29310
  ...data,
29186
29311
  timestamp: Date.now(),
29187
29312
  platform: os.platform(),
29188
- nodeVersion: process36.version
29313
+ nodeVersion: process33.version
29189
29314
  };
29190
29315
  this.events.push(telemetryEvent);
29191
29316
  if (this.events.length >= 10) {
@@ -29241,7 +29366,7 @@ class Telemetry {
29241
29366
  const config6 = await this.loadConfig();
29242
29367
  return {
29243
29368
  enabled: config6.enabled,
29244
- doNotTrack: process36.env.DO_NOT_TRACK === "1" || process36.env.DO_NOT_TRACK === "true",
29369
+ doNotTrack: process33.env.DO_NOT_TRACK === "1" || process33.env.DO_NOT_TRACK === "true",
29245
29370
  eventsQueued: this.events.length,
29246
29371
  lastSent: config6.lastSent
29247
29372
  };
@@ -29251,7 +29376,7 @@ class Telemetry {
29251
29376
  return this.config;
29252
29377
  }
29253
29378
  try {
29254
- if (existsSync23(this.configPath)) {
29379
+ if (existsSync25(this.configPath)) {
29255
29380
  const data = await fs.readFile(this.configPath, "utf-8");
29256
29381
  this.config = JSON.parse(data);
29257
29382
  return this.config;
@@ -31163,7 +31288,7 @@ function getPrefix() {
31163
31288
  }
31164
31289
  var prefix = getPrefix();
31165
31290
  // package.json
31166
- var version2 = "0.1.63";
31291
+ var version2 = "0.2.0";
31167
31292
 
31168
31293
  // bin/cli.ts
31169
31294
  init_actions();
@@ -31218,7 +31343,7 @@ cli.command("unsafe <sql>", "Execute an unsafe SQL string (one statement with pa
31218
31343
  cli.command("file <path>", "Execute a SQL file (supports $1, $2 params)").option("--params <json>", "JSON array of parameters").example("query-builder file ./migrations/seed.sql").example('query-builder file ./reports/top.sql --params "[30]"').action(async (path2, opts) => {
31219
31344
  await file(path2, opts);
31220
31345
  });
31221
- cli.command("migrate <dir>", "Generate SQL migrations from models").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").option("--apply", "Execute the generated SQL against the database").option("--full", "Force full migration SQL instead of incremental diff").example("query-builder migrate ./app/Models --dialect postgres").action(async (dir, opts) => {
31346
+ cli.command("migrate <dir>", "Generate SQL migrations from models").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").option("--apply", "Execute the generated SQL against the database").option("--full", "Force full migration SQL instead of incremental diff").example("query-builder migrate ./app/Models --dialect postgres").action(async (dir, opts) => {
31222
31347
  try {
31223
31348
  await generateMigration(dir, {
31224
31349
  dialect: opts.dialect,
@@ -31262,7 +31387,7 @@ cli.command("relation:diagram", "Generate relationship diagram from models").opt
31262
31387
  proc.default.exitCode = 1;
31263
31388
  }
31264
31389
  });
31265
- cli.command("migrate:fresh <dir>", "Reset database and run all migrations").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").option("--full", "Force full migration SQL instead of incremental diff").example("query-builder migrate:fresh ./app/Models --dialect postgres").action(async (dir, opts) => {
31390
+ cli.command("migrate:fresh <dir>", "Reset database and run all migrations").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").option("--full", "Force full migration SQL instead of incremental diff").example("query-builder migrate:fresh ./app/Models --dialect postgres").action(async (dir, opts) => {
31266
31391
  try {
31267
31392
  console.log("-- Resetting database...");
31268
31393
  await resetDatabase(dir, {
@@ -31283,7 +31408,7 @@ cli.command("migrate:fresh <dir>", "Reset database and run all migrations").opti
31283
31408
  proc.default.exitCode = 1;
31284
31409
  }
31285
31410
  });
31286
- cli.command("reset <dir>", "Drop all tables and reset database").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").example("query-builder reset ./app/Models --dialect postgres").action(async (dir, opts) => {
31411
+ cli.command("reset <dir>", "Drop all tables and reset database").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--state <path>", "Path to migration state file (defaults to <dir>/.qb-migrations.<dialect>.json)").example("query-builder reset ./app/Models --dialect postgres").action(async (dir, opts) => {
31287
31412
  try {
31288
31413
  await resetDatabase(dir, {
31289
31414
  dialect: opts.dialect,
@@ -31387,16 +31512,20 @@ cli.command("migrate:list", "List all migrations (alias for migrate:status)").ex
31387
31512
  proc.default.exitCode = 1;
31388
31513
  }
31389
31514
  });
31390
- cli.command("migrate:rollback", "Rollback migrations").option("--steps <n>", "Number of migrations to rollback", { default: 1 }).example("query-builder migrate:rollback").example("query-builder migrate:rollback --steps 2").action(async (opts) => {
31515
+ cli.command("migrate:rollback", "Rollback migrations").option("--steps <n>", "Number of migrations to rollback", { default: 1 }).option("--no-reverse-schema", "Only remove the migration records; leave the schema alone").option("--delete-files", "Also delete the rolled-back migration files from disk").example("query-builder migrate:rollback").example("query-builder migrate:rollback --steps 2").example("query-builder migrate:rollback --no-reverse-schema").action(async (opts) => {
31391
31516
  try {
31392
- await migrateRollback({ steps: Number(opts.steps) });
31517
+ await migrateRollback({
31518
+ steps: Number(opts.steps),
31519
+ reverseSchema: opts.reverseSchema,
31520
+ deleteFiles: Boolean(opts.deleteFiles)
31521
+ });
31393
31522
  } catch (err) {
31394
31523
  console.error("-- Rollback failed:", err);
31395
31524
  const proc = await import("process");
31396
31525
  proc.default.exitCode = 1;
31397
31526
  }
31398
31527
  });
31399
- cli.command("migrate:generate [dir]", "Generate migration from model changes").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--state <path>", "Path to migration state file").option("--apply", "Execute the generated SQL").option("--full", "Force full migration SQL").example("query-builder migrate:generate").example("query-builder migrate:generate ./app/Models --dialect postgres").action(async (dir, opts) => {
31528
+ cli.command("migrate:generate [dir]", "Generate migration from model changes").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--state <path>", "Path to migration state file").option("--apply", "Execute the generated SQL").option("--full", "Force full migration SQL").example("query-builder migrate:generate").example("query-builder migrate:generate ./app/Models --dialect postgres").action(async (dir, opts) => {
31400
31529
  try {
31401
31530
  await migrateGenerate(dir, {
31402
31531
  dialect: opts?.dialect,
@@ -31428,7 +31557,7 @@ cli.command("db:stats", "Show database statistics (alias for db:info)").example(
31428
31557
  proc.default.exitCode = 1;
31429
31558
  }
31430
31559
  });
31431
- cli.command("db:wipe", "Drop all tables from the database").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--force", "Skip confirmation prompt").option("--verbose", "Enable verbose output").example("query-builder db:wipe").example("query-builder db:wipe --force").action(async (opts) => {
31560
+ cli.command("db:wipe", "Drop all tables from the database").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--force", "Skip confirmation prompt").option("--verbose", "Enable verbose output").example("query-builder db:wipe").example("query-builder db:wipe --force").action(async (opts) => {
31432
31561
  try {
31433
31562
  await dbWipe({
31434
31563
  dialect: opts.dialect,
@@ -31441,7 +31570,7 @@ cli.command("db:wipe", "Drop all tables from the database").option("--dialect <d
31441
31570
  proc.default.exitCode = 1;
31442
31571
  }
31443
31572
  });
31444
- cli.command("db:optimize", "Optimize database tables (VACUUM, ANALYZE, OPTIMIZE)").option("--dialect <d>", "Dialect (postgres|mysql|sqlite)", { default: "postgres" }).option("--aggressive", "Use aggressive optimization (VACUUM FULL for postgres)").option("--tables <tables>", "Comma-separated list of tables to optimize").option("--verbose", "Enable verbose output").example("query-builder db:optimize").example("query-builder db:optimize --aggressive").example("query-builder db:optimize --tables users,posts").action(async (opts) => {
31573
+ cli.command("db:optimize", "Optimize database tables (VACUUM, ANALYZE, OPTIMIZE)").option("--dialect <d>", "Dialect (postgres|mysql|sqlite) \u2014 defaults to the configured dialect").option("--aggressive", "Use aggressive optimization (VACUUM FULL for postgres)").option("--tables <tables>", "Comma-separated list of tables to optimize").option("--verbose", "Enable verbose output").example("query-builder db:optimize").example("query-builder db:optimize --aggressive").example("query-builder db:optimize --tables users,posts").action(async (opts) => {
31445
31574
  try {
31446
31575
  await dbOptimize({
31447
31576
  dialect: opts.dialect,