bun-query-builder 0.1.62 → 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/src/index.js CHANGED
@@ -11640,9 +11640,18 @@ var init_dist = __esm(() => {
11640
11640
  });
11641
11641
 
11642
11642
  // src/config.ts
11643
+ import process19 from "process";
11643
11644
  function isMysqlLike(dialect = config5.dialect) {
11644
11645
  return dialect === "mysql" || dialect === "singlestore";
11645
11646
  }
11647
+ function resolveDialect(explicit) {
11648
+ if (explicit)
11649
+ return explicit;
11650
+ const fromEnv = process19.env.DB_DIALECT || process19.env.DB_CONNECTION;
11651
+ if (fromEnv)
11652
+ return fromEnv;
11653
+ return config5.dialect || "postgres";
11654
+ }
11646
11655
  function getPlaceholder(index) {
11647
11656
  if (config5.dialect === "postgres") {
11648
11657
  return `$${index}`;
@@ -11661,26 +11670,23 @@ function getPlaceholders(count, startIndex = 1) {
11661
11670
  return count === 1 ? "?" : `?${", ?".repeat(count - 1)}`;
11662
11671
  }
11663
11672
  async function getConfig() {
11664
- if (!_config) {
11665
- _config = await loadConfig6({
11673
+ if (!configState.fileLoaded) {
11674
+ configState.fileLoaded = true;
11675
+ const loaded = await loadConfig6({
11666
11676
  name: "query-builder",
11667
11677
  alias: "qb",
11668
11678
  defaultConfig: defaultConfig4
11669
11679
  });
11670
- setConfig(_config);
11680
+ const applicable = {};
11681
+ for (const [key, value] of Object.entries(loaded ?? {})) {
11682
+ if (!configState.explicit.has(key))
11683
+ applicable[key] = value;
11684
+ }
11685
+ applyConfig(applicable);
11671
11686
  }
11672
11687
  return config5;
11673
11688
  }
11674
- function setConfig(userConfig) {
11675
- if (userConfig.dialect && _lastConfiguredDialect && userConfig.dialect !== _lastConfiguredDialect) {
11676
- const key = `${_lastConfiguredDialect}->${userConfig.dialect}`;
11677
- if (!_warnedDialectConflicts.has(key)) {
11678
- _warnedDialectConflicts.add(key);
11679
- console.warn(`[query-builder] setConfig({ dialect: '${userConfig.dialect}' }) overrides a previous ` + `setConfig({ dialect: '${_lastConfiguredDialect}' }). ` + `Config is process-wide; in-flight queries from the previous configuration may break. ` + `Run conflicting connections in separate processes \u2014 see stacksjs/stacks#1862 #18.`);
11680
- }
11681
- }
11682
- if (userConfig.dialect)
11683
- _lastConfiguredDialect = userConfig.dialect;
11689
+ function applyConfig(userConfig) {
11684
11690
  Object.assign(config5, userConfig);
11685
11691
  if (userConfig.database) {
11686
11692
  config5.database = { ...config5.database, ...userConfig.database };
@@ -11694,11 +11700,22 @@ function setConfig(userConfig) {
11694
11700
  if (userConfig.softDeletes) {
11695
11701
  config5.softDeletes = { ...config5.softDeletes, ...userConfig.softDeletes };
11696
11702
  }
11697
- if (_config) {
11698
- Object.assign(_config, config5);
11703
+ }
11704
+ function setConfig(userConfig) {
11705
+ if (userConfig.dialect && _lastConfiguredDialect && userConfig.dialect !== _lastConfiguredDialect) {
11706
+ const key = `${_lastConfiguredDialect}->${userConfig.dialect}`;
11707
+ if (!_warnedDialectConflicts.has(key)) {
11708
+ _warnedDialectConflicts.add(key);
11709
+ console.warn(`[query-builder] setConfig({ dialect: '${userConfig.dialect}' }) overrides a previous ` + `setConfig({ dialect: '${_lastConfiguredDialect}' }). ` + `Config is process-wide; in-flight queries from the previous configuration may break. ` + `Run conflicting connections in separate processes \u2014 see stacksjs/stacks#1862 #18.`);
11710
+ }
11699
11711
  }
11712
+ if (userConfig.dialect)
11713
+ _lastConfiguredDialect = userConfig.dialect;
11714
+ for (const key of Object.keys(userConfig))
11715
+ configState.explicit.add(key);
11716
+ applyConfig(userConfig);
11700
11717
  }
11701
- var defaultConfig4, CONFIG_SINGLETON_KEY, config5, _config = null, _lastConfiguredDialect = null, _warnedDialectConflicts;
11718
+ var defaultConfig4, CONFIG_SINGLETON_KEY, config5, CONFIG_STATE_KEY, configState, _lastConfiguredDialect = null, _warnedDialectConflicts;
11702
11719
  var init_config = __esm(() => {
11703
11720
  init_dist();
11704
11721
  defaultConfig4 = {
@@ -11761,6 +11778,8 @@ var init_config = __esm(() => {
11761
11778
  };
11762
11779
  CONFIG_SINGLETON_KEY = Symbol.for("bun-query-builder.config");
11763
11780
  config5 = globalThis[CONFIG_SINGLETON_KEY] ??= { ...defaultConfig4 };
11781
+ CONFIG_STATE_KEY = Symbol.for("bun-query-builder.config-state");
11782
+ configState = globalThis[CONFIG_STATE_KEY] ??= { explicit: new Set, fileLoaded: false };
11764
11783
  _warnedDialectConflicts = new Set;
11765
11784
  });
11766
11785
 
@@ -11791,7 +11810,7 @@ var {SQL } = globalThis.Bun;
11791
11810
  import { Database } from "bun:sqlite";
11792
11811
  import { mkdirSync as mkdirSync5 } from "fs";
11793
11812
  import { dirname as dirname5 } from "path";
11794
- import process19 from "process";
11813
+ import process21 from "process";
11795
11814
 
11796
11815
  class SQLiteWrapper {
11797
11816
  db;
@@ -12069,9 +12088,13 @@ function sqliteFileFromUrl(url) {
12069
12088
  const path = stripped.startsWith("///") ? stripped.slice(2) : stripped.startsWith("//") ? stripped.slice(2) : stripped;
12070
12089
  return path || ":memory:";
12071
12090
  }
12091
+ function resolveDatabaseName(dbConfig = config5.database) {
12092
+ const raw = process21.env.DB_DATABASE || dbConfig?.database || "";
12093
+ return String(raw).replace(/^['"]|['"]$/g, "");
12094
+ }
12072
12095
  function createConnectionString(dialect, dbConfig) {
12073
- if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process19.env.DB_CONNECTION === dialect) {
12074
- const e = process19.env;
12096
+ if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process21.env.DB_CONNECTION === dialect) {
12097
+ const e = process21.env;
12075
12098
  const envDb = (e.DB_DATABASE || dbConfig.database || "").replace(/^['"]|['"]$/g, "");
12076
12099
  const envUser = e.DB_USERNAME || dbConfig.username;
12077
12100
  const envPass = e.DB_PASSWORD ?? dbConfig.password ?? "";
@@ -12085,7 +12108,7 @@ function createConnectionString(dialect, dbConfig) {
12085
12108
  return dialect === "sqlite" ? sqliteFileFromUrl(dbConfig.url) : dbConfig.url;
12086
12109
  }
12087
12110
  const { database, username, password, host = "localhost", port, ssl } = dbConfig;
12088
- const sslQ = ssl || process19.env.DB_SSL === "true" || process19.env.DB_SSL === "1" ? "?ssl=true" : "";
12111
+ const sslQ = ssl || process21.env.DB_SSL === "true" || process21.env.DB_SSL === "1" ? "?ssl=true" : "";
12089
12112
  switch (dialect) {
12090
12113
  case "postgres":
12091
12114
  return `postgres://${username}:${password}@${host}${port ? `:${port}` : ""}/${database}${sslQ}`;
@@ -12359,6 +12382,17 @@ var init_benchmark = __esm(() => {
12359
12382
  });
12360
12383
 
12361
12384
  // src/inflect.ts
12385
+ function toTableName(modelName) {
12386
+ const snake = String(modelName).replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
12387
+ if (snake.endsWith("y") && !/[aeou]y$/.test(snake))
12388
+ return `${snake.slice(0, -1)}ies`;
12389
+ if (snake.endsWith("s") || snake.endsWith("x") || snake.endsWith("ch") || snake.endsWith("sh"))
12390
+ return `${snake}es`;
12391
+ return `${snake}s`;
12392
+ }
12393
+ function tableNameFor(model) {
12394
+ return typeof model?.table === "string" && model.table.length > 0 ? model.table : toTableName(String(model?.name ?? ""));
12395
+ }
12362
12396
  function stripTrailingS(name) {
12363
12397
  return name.endsWith("s") ? name.slice(0, -1) : name;
12364
12398
  }
@@ -16579,7 +16613,7 @@ var init_cache = __esm(() => {
16579
16613
  });
16580
16614
 
16581
16615
  // src/actions/console.ts
16582
- import process21 from "process";
16616
+ import process22 from "process";
16583
16617
  import { createInterface } from "readline";
16584
16618
  async function startConsole() {
16585
16619
  console.log("-- Query Builder Interactive Console");
@@ -16588,8 +16622,8 @@ async function startConsole() {
16588
16622
  console.log();
16589
16623
  const qb = createQueryBuilder();
16590
16624
  const rl = createInterface({
16591
- input: process21.stdin,
16592
- output: process21.stdout,
16625
+ input: process22.stdin,
16626
+ output: process22.stdout,
16593
16627
  prompt: "qb> "
16594
16628
  });
16595
16629
  let multilineBuffer = "";
@@ -16640,7 +16674,7 @@ Tips:
16640
16674
  if (cmd === ".exit" || cmd === ".quit") {
16641
16675
  console.log("Goodbye!");
16642
16676
  rl.close();
16643
- process21.exit(0);
16677
+ process22.exit(0);
16644
16678
  } else if (cmd === ".help") {
16645
16679
  console.log(helpText);
16646
16680
  } else if (cmd === ".clear") {
@@ -16711,7 +16745,7 @@ Tips:
16711
16745
  rl.on("close", () => {
16712
16746
  console.log(`
16713
16747
  Goodbye!`);
16714
- process21.exit(0);
16748
+ process22.exit(0);
16715
16749
  });
16716
16750
  rl.prompt();
16717
16751
  }
@@ -17089,9 +17123,8 @@ var init_db_info = __esm(() => {
17089
17123
  });
17090
17124
 
17091
17125
  // src/actions/db-optimize.ts
17092
- import process22 from "process";
17093
17126
  async function dbOptimize(options = {}) {
17094
- const dialect = options.dialect || process22.env.DB_DIALECT || config5.dialect || "postgres";
17127
+ const dialect = resolveDialect(options.dialect);
17095
17128
  const aggressive = options.aggressive || false;
17096
17129
  if (options.verbose) {
17097
17130
  console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
@@ -17131,7 +17164,7 @@ async function dbOptimize(options = {}) {
17131
17164
  await bunSql`ANALYZE TABLE ${bunSql(table)}`;
17132
17165
  }
17133
17166
  } else {
17134
- const dbName = process22.env.DB_NAME || "test";
17167
+ const dbName = resolveDatabaseName();
17135
17168
  const tables = await bunSql`
17136
17169
  SELECT table_name
17137
17170
  FROM information_schema.tables
@@ -17184,9 +17217,8 @@ var init_db_optimize = __esm(() => {
17184
17217
  });
17185
17218
 
17186
17219
  // src/actions/db-wipe.ts
17187
- import process23 from "process";
17188
17220
  async function dbWipe(options = {}) {
17189
- const dialect = options.dialect || process23.env.DB_DIALECT || config5.dialect || "postgres";
17221
+ const dialect = resolveDialect(options.dialect);
17190
17222
  if (options.verbose) {
17191
17223
  console.log(`Wiping all tables from ${dialect} database...`);
17192
17224
  }
@@ -17200,7 +17232,7 @@ async function dbWipe(options = {}) {
17200
17232
  `;
17201
17233
  tables = result.map((row) => row.tablename);
17202
17234
  } else if (isMysqlLike(dialect)) {
17203
- const dbName = process23.env.DB_NAME || "test";
17235
+ const dbName = resolveDatabaseName();
17204
17236
  const result = await bunSql`
17205
17237
  SELECT table_name
17206
17238
  FROM information_schema.tables
@@ -17637,6 +17669,8 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17637
17669
  return "decimal";
17638
17670
  if (base === "date")
17639
17671
  return "date";
17672
+ if (base === "timestamptz" || /\bwith time zone\b/.test(t))
17673
+ return "timestamptz";
17640
17674
  if (/^(?:timestamp|datetime)/.test(base))
17641
17675
  return "datetime";
17642
17676
  if (/^time/.test(base))
@@ -17892,7 +17926,7 @@ var init_introspect_db = __esm(() => {
17892
17926
  // src/actions/make-model.ts
17893
17927
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
17894
17928
  import { dirname as dirname7, join as join7 } from "path";
17895
- import process24 from "process";
17929
+ import process23 from "process";
17896
17930
  function findWorkspaceRoot(startPath) {
17897
17931
  let currentPath = startPath;
17898
17932
  while (currentPath !== dirname7(currentPath)) {
@@ -17901,10 +17935,10 @@ function findWorkspaceRoot(startPath) {
17901
17935
  }
17902
17936
  currentPath = dirname7(currentPath);
17903
17937
  }
17904
- return process24.cwd();
17938
+ return process23.cwd();
17905
17939
  }
17906
17940
  async function makeModel(name, options = {}) {
17907
- const workspaceRoot = findWorkspaceRoot(process24.cwd());
17941
+ const workspaceRoot = findWorkspaceRoot(process23.cwd());
17908
17942
  const modelsDir = options.dir || join7(workspaceRoot, "app/Models");
17909
17943
  if (!existsSync14(modelsDir)) {
17910
17944
  mkdirSync7(modelsDir, { recursive: true });
@@ -18100,7 +18134,7 @@ class MySQLDriver {
18100
18134
  )`;
18101
18135
  }
18102
18136
  getExecutedMigrationsQuery() {
18103
- return "SELECT migration FROM migrations ORDER BY executed_at";
18137
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18104
18138
  }
18105
18139
  recordMigrationQuery() {
18106
18140
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18281,7 +18315,7 @@ class PostgresDriver {
18281
18315
  )`;
18282
18316
  }
18283
18317
  getExecutedMigrationsQuery() {
18284
- return "SELECT migration FROM migrations ORDER BY executed_at";
18318
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18285
18319
  }
18286
18320
  recordMigrationQuery() {
18287
18321
  return "INSERT INTO migrations (migration) VALUES ($1)";
@@ -18526,7 +18560,7 @@ class SQLiteDriver {
18526
18560
  )`;
18527
18561
  }
18528
18562
  getExecutedMigrationsQuery() {
18529
- return "SELECT migration FROM migrations ORDER BY executed_at";
18563
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18530
18564
  }
18531
18565
  recordMigrationQuery() {
18532
18566
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18579,6 +18613,24 @@ var init_drivers = __esm(() => {
18579
18613
  init_singlestore();
18580
18614
  });
18581
18615
 
18616
+ // src/workspace.ts
18617
+ import { existsSync as existsSync16 } from "fs";
18618
+ import { dirname as dirname8, join as join9 } from "path";
18619
+ import process24 from "process";
18620
+ function findWorkspaceRoot2(startPath = process24.cwd()) {
18621
+ let currentPath = startPath;
18622
+ while (currentPath !== dirname8(currentPath)) {
18623
+ if (existsSync16(join9(currentPath, "package.json")))
18624
+ return currentPath;
18625
+ currentPath = dirname8(currentPath);
18626
+ }
18627
+ return process24.cwd();
18628
+ }
18629
+ function getSqlDirectory(workspaceRoot) {
18630
+ return join9(workspaceRoot ?? findWorkspaceRoot2(), "database", "migrations");
18631
+ }
18632
+ var init_workspace = () => {};
18633
+
18582
18634
  // src/actions/migrate.ts
18583
18635
  var exports_migrate = {};
18584
18636
  __export(exports_migrate, {
@@ -18590,23 +18642,48 @@ __export(exports_migrate, {
18590
18642
  copyModelsToGenerated: () => copyModelsToGenerated,
18591
18643
  clearGeneratedDirectory: () => clearGeneratedDirectory
18592
18644
  });
18593
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18645
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18594
18646
  import { tmpdir } from "os";
18595
- import { join as join9 } from "path";
18647
+ import { join as join10 } from "path";
18596
18648
  import process25 from "process";
18649
+ async function settleAppliedFiles(qb, dialect, files) {
18650
+ if (files.length === 0)
18651
+ return;
18652
+ const sqlDir = ensureSqlDirectory(getWorkspaceRoot());
18653
+ try {
18654
+ await createMigrationsTable(qb, dialect);
18655
+ for (const file2 of files) {
18656
+ await recordMigration(qb, file2, dialect);
18657
+ info(`-- \u2713 Recorded applied migration: ${file2}`);
18658
+ }
18659
+ } catch (err) {
18660
+ info(`-- Could not record applied migrations (the schema change itself succeeded): ${err instanceof Error ? err.message : String(err)}`);
18661
+ }
18662
+ }
18663
+ function isGeneratedMigration(sqlDir, file2) {
18664
+ try {
18665
+ return isGeneratedMigrationSql(readFileSync3(join10(sqlDir, file2), "utf8"));
18666
+ } catch {
18667
+ return false;
18668
+ }
18669
+ }
18670
+ function isAlreadyAppliedError(err) {
18671
+ const message = err instanceof Error ? err.message : String(err);
18672
+ return /duplicate column name/i.test(message) || /duplicate key name/i.test(message) || /\balready exists\b/i.test(message);
18673
+ }
18597
18674
  function info(message) {
18598
18675
  if (config5.verbose)
18599
18676
  console.log(message);
18600
18677
  }
18601
18678
  function getSnapshotDir(workspaceRoot) {
18602
- return join9(workspaceRoot, config5.snapshotDir || ".qb");
18679
+ return join10(workspaceRoot, config5.snapshotDir || ".qb");
18603
18680
  }
18604
18681
  function getSnapshotPath(workspaceRoot, dialect) {
18605
- return join9(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18682
+ return join10(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18606
18683
  }
18607
18684
  function loadPlanSnapshot(workspaceRoot, dialect) {
18608
18685
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18609
- if (!existsSync16(snapshotPath)) {
18686
+ if (!existsSync17(snapshotPath)) {
18610
18687
  return;
18611
18688
  }
18612
18689
  try {
@@ -18629,7 +18706,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18629
18706
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18630
18707
  const snapshotDir = getSnapshotDir(workspaceRoot);
18631
18708
  const hash = hashMigrationPlan(plan);
18632
- if (existsSync16(snapshotPath)) {
18709
+ if (existsSync17(snapshotPath)) {
18633
18710
  try {
18634
18711
  const current = JSON.parse(readFileSync3(snapshotPath, "utf8"));
18635
18712
  if (current?.hash === hash && current?.dialect === dialect && current?.plan) {
@@ -18638,7 +18715,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18638
18715
  }
18639
18716
  } catch {}
18640
18717
  }
18641
- if (!existsSync16(snapshotDir)) {
18718
+ if (!existsSync17(snapshotDir)) {
18642
18719
  mkdirSync8(snapshotDir, { recursive: true });
18643
18720
  info(`-- Created snapshot directory: ${snapshotDir}`);
18644
18721
  }
@@ -18656,11 +18733,11 @@ function saveMigrationSnapshot(plan, options = {}) {
18656
18733
  savePlanSnapshot(options.workspaceRoot ?? getWorkspaceRoot(), dialect, plan);
18657
18734
  }
18658
18735
  function getWorkspaceRoot() {
18659
- return process25.cwd();
18736
+ return findWorkspaceRoot2();
18660
18737
  }
18661
18738
  function ensureSqlDirectory(workspaceRoot) {
18662
18739
  const sqlDir = getSqlDirectory(workspaceRoot);
18663
- if (!existsSync16(sqlDir)) {
18740
+ if (!existsSync17(sqlDir)) {
18664
18741
  mkdirSync8(sqlDir, { recursive: true });
18665
18742
  info(`-- Created SQL directory: ${sqlDir}`);
18666
18743
  }
@@ -18669,21 +18746,22 @@ function ensureSqlDirectory(workspaceRoot) {
18669
18746
  async function generateMigration(dir, opts = {}) {
18670
18747
  await getConfig();
18671
18748
  if (!dir) {
18672
- dir = join9(process25.cwd(), "app/Models");
18749
+ dir = join10(process25.cwd(), "app/Models");
18673
18750
  }
18674
18751
  const dialect = opts.dialect || config5.dialect || "postgres";
18675
18752
  const workspaceRoot = getWorkspaceRoot();
18676
18753
  const models = await loadModels({ modelsDir: dir });
18677
18754
  const plan = buildMigrationPlan2(models, { dialect });
18678
18755
  let previous;
18756
+ let reconciledFromDatabase = false;
18679
18757
  if (!opts.full) {
18680
18758
  previous = loadPlanSnapshot(workspaceRoot, dialect);
18681
18759
  if (previous) {
18682
18760
  info("-- Comparing with stored model snapshot");
18683
18761
  } else {
18684
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
18762
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18685
18763
  const statePath = String(opts.state || defaultStatePath);
18686
- if (existsSync16(statePath)) {
18764
+ if (existsSync17(statePath)) {
18687
18765
  try {
18688
18766
  const raw2 = readFileSync3(statePath, "utf8");
18689
18767
  const parsed = JSON.parse(raw2);
@@ -18700,6 +18778,7 @@ async function generateMigration(dir, opts = {}) {
18700
18778
  const scoped = livePlan.tables.filter((t) => modelTables.has(t.table));
18701
18779
  if (scoped.length > 0) {
18702
18780
  previous = { dialect: livePlan.dialect, tables: scoped };
18781
+ reconciledFromDatabase = true;
18703
18782
  info(`-- No snapshot; reconciling against live database (${scoped.length} matching table${scoped.length === 1 ? "" : "s"})`);
18704
18783
  }
18705
18784
  } catch (err) {
@@ -18713,25 +18792,34 @@ async function generateMigration(dir, opts = {}) {
18713
18792
  } else {
18714
18793
  info("-- Full migration requested, ignoring any previous state");
18715
18794
  }
18716
- 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 });
18795
+ 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, {
18796
+ applyRenames: opts.applyRenames,
18797
+ dryRun: opts.dryRun,
18798
+ preserveUnknownColumns: reconciledFromDatabase
18799
+ });
18717
18800
  const sqlStatements = diff.statements;
18718
18801
  const operations = diff.operations;
18719
18802
  const sql = sqlStatements.join(`
18720
18803
  `);
18721
18804
  const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER|DROP)\b/i.test(stmt));
18722
18805
  if (opts.apply) {
18723
- const dirPath = mkdtempSync(join9(tmpdir(), "qb-migrate-"));
18724
- const filePath = join9(dirPath, "migration.sql");
18806
+ const dirPath = mkdtempSync(join10(tmpdir(), "qb-migrate-"));
18807
+ const filePath = join10(dirPath, "migration.sql");
18725
18808
  try {
18726
18809
  if (hasChanges) {
18727
18810
  writeFileSync11(filePath, sql);
18811
+ const qb = createQueryBuilder();
18812
+ await qb.file(filePath);
18728
18813
  info("-- Migration applied");
18814
+ await settleAppliedFiles(qb, dialect, lastCreatedMigrationFiles());
18729
18815
  } else {
18730
18816
  info("-- No changes; nothing to apply");
18731
18817
  }
18732
18818
  } catch (err) {
18733
18819
  console.error("-- Migration failed:", err);
18734
18820
  throw err;
18821
+ } finally {
18822
+ rmSync(dirPath, { recursive: true, force: true });
18735
18823
  }
18736
18824
  }
18737
18825
  if (!opts.dryRun)
@@ -18740,9 +18828,6 @@ async function generateMigration(dir, opts = {}) {
18740
18828
  }
18741
18829
  async function executeMigration(dir) {
18742
18830
  await getConfig();
18743
- if (!dir) {
18744
- dir = join9(process25.cwd(), "app/Models");
18745
- }
18746
18831
  const workspaceRoot = getWorkspaceRoot();
18747
18832
  const sqlDir = ensureSqlDirectory(workspaceRoot);
18748
18833
  const dialect = config5.dialect || "postgres";
@@ -18757,42 +18842,25 @@ async function executeMigration(dir) {
18757
18842
  const qb = createQueryBuilder();
18758
18843
  await createMigrationsTable(qb, dialect);
18759
18844
  const executedMigrations = await getExecutedMigrations(qb, dialect);
18760
- const permanentMigrations = [];
18761
- const transientMigrations = [];
18762
- for (const file2 of scriptFiles) {
18763
- if (file2.includes("alter-") && file2.includes("-table")) {
18764
- transientMigrations.push(file2);
18765
- } else if (!executedMigrations.includes(file2)) {
18766
- permanentMigrations.push(file2);
18767
- }
18768
- }
18769
- const totalPending = permanentMigrations.length + transientMigrations.length;
18770
- if (totalPending === 0) {
18845
+ const pending = scriptFiles.filter((file2) => !executedMigrations.includes(file2));
18846
+ if (pending.length === 0) {
18771
18847
  info("-- No pending migrations to execute");
18772
18848
  return true;
18773
18849
  }
18774
- info(`-- Executing ${totalPending} migrations (${permanentMigrations.length} permanent, ${transientMigrations.length} transient)`);
18775
- for (const file2 of permanentMigrations) {
18776
- const filePath = join9(sqlDir, file2);
18850
+ info(`-- Executing ${pending.length} migration${pending.length === 1 ? "" : "s"}`);
18851
+ for (const file2 of pending) {
18852
+ const filePath = join10(sqlDir, file2);
18777
18853
  info(`-- Executing: ${file2}`);
18778
18854
  try {
18779
18855
  await qb.file(filePath);
18780
18856
  await recordMigration(qb, file2, dialect);
18781
18857
  info(`-- \u2713 Migration ${file2} executed and recorded`);
18782
18858
  } catch (err) {
18783
- console.error(`-- \u2717 Migration ${file2} failed:`, err);
18784
- throw err;
18785
- }
18786
- }
18787
- for (const file2 of transientMigrations) {
18788
- const filePath = join9(sqlDir, file2);
18789
- info(`-- Executing: ${file2} (transient)`);
18790
- try {
18791
- await qb.file(filePath);
18792
- info(`-- \u2713 Migration ${file2} executed (not recorded)`);
18793
- unlinkSync(filePath);
18794
- info(`-- \uD83D\uDDD1\uFE0F Deleted transient migration: ${file2}`);
18795
- } catch (err) {
18859
+ if (isGeneratedMigration(sqlDir, file2) && isAlreadyAppliedError(err)) {
18860
+ await recordMigration(qb, file2, dialect);
18861
+ info(`-- \u2713 Migration ${file2} was already applied; recorded it (${err instanceof Error ? err.message : String(err)})`);
18862
+ continue;
18863
+ }
18796
18864
  console.error(`-- \u2717 Migration ${file2} failed:`, err);
18797
18865
  throw err;
18798
18866
  }
@@ -18807,7 +18875,7 @@ async function executeMigration(dir) {
18807
18875
  async function resetDatabase(dir, opts = {}) {
18808
18876
  await getConfig();
18809
18877
  if (!dir) {
18810
- dir = join9(process25.cwd(), "app/Models");
18878
+ dir = join10(process25.cwd(), "app/Models");
18811
18879
  }
18812
18880
  const dialect = opts.dialect || config5.dialect || "postgres";
18813
18881
  const driver = getDialectDriver(dialect);
@@ -18904,42 +18972,50 @@ async function resetDatabase(dir, opts = {}) {
18904
18972
  }
18905
18973
  async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
18906
18974
  if (!dir) {
18907
- dir = join9(process25.cwd(), "app/Models");
18975
+ dir = join10(process25.cwd(), "app/Models");
18908
18976
  }
18909
18977
  if (!workspaceRoot) {
18910
18978
  workspaceRoot = getWorkspaceRoot();
18911
18979
  }
18912
18980
  const dialect = String(opts.dialect || config5.dialect || "postgres");
18913
18981
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18914
- if (existsSync16(snapshotPath)) {
18982
+ if (existsSync17(snapshotPath)) {
18915
18983
  unlinkSync(snapshotPath);
18916
18984
  info(`-- Removed model snapshot file: ${snapshotPath}`);
18917
18985
  }
18918
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
18986
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18919
18987
  const statePath = String(opts.state || defaultStatePath);
18920
- if (existsSync16(statePath)) {
18988
+ if (existsSync17(statePath)) {
18921
18989
  unlinkSync(statePath);
18922
18990
  info(`-- Removed legacy migration state file: ${statePath}`);
18923
18991
  }
18924
- const sqlDir = getSqlDirectory(workspaceRoot);
18925
- if (existsSync16(sqlDir)) {
18926
- const sqlFiles = readdirSync6(sqlDir);
18927
- const migrationFiles = sqlFiles.filter((file2) => file2.endsWith(".sql"));
18928
- for (const file2 of migrationFiles) {
18929
- const filePath = join9(sqlDir, file2);
18930
- unlinkSync(filePath);
18931
- info(`-- Removed migration file: ${file2}`);
18992
+ removeGeneratedMigrationFiles(getSqlDirectory(workspaceRoot));
18993
+ }
18994
+ function removeGeneratedMigrationFiles(sqlDir) {
18995
+ if (!existsSync17(sqlDir))
18996
+ return;
18997
+ let removed = 0;
18998
+ const kept = [];
18999
+ for (const file2 of readdirSync6(sqlDir).filter((f) => f.endsWith(".sql"))) {
19000
+ if (!isGeneratedMigration(sqlDir, file2)) {
19001
+ kept.push(file2);
19002
+ continue;
18932
19003
  }
18933
- info(`-- Cleaned up ${migrationFiles.length} migration files from migrations directory`);
19004
+ unlinkSync(join10(sqlDir, file2));
19005
+ info(`-- Removed generated migration file: ${file2}`);
19006
+ removed += 1;
18934
19007
  }
19008
+ info(`-- Cleaned up ${removed} generated migration file${removed === 1 ? "" : "s"} from migrations directory`);
19009
+ if (kept.length > 0)
19010
+ info(`-- Kept ${kept.length} hand-written migration${kept.length === 1 ? "" : "s"}: ${kept.slice(0, 5).join(", ")}${kept.length > 5 ? `, +${kept.length - 5} more` : ""}`);
18935
19011
  }
18936
19012
  async function copyModelsToGenerated(_dir, _workspaceRoot) {}
18937
19013
  async function clearGeneratedDirectory(workspaceRoot) {
18938
19014
  if (!workspaceRoot) {
18939
19015
  workspaceRoot = getWorkspaceRoot();
18940
19016
  }
18941
- const generatedDir = join9(workspaceRoot, "generated");
18942
- if (existsSync16(generatedDir)) {
19017
+ const generatedDir = join10(workspaceRoot, "generated");
19018
+ if (existsSync17(generatedDir)) {
18943
19019
  try {
18944
19020
  rmSync(generatedDir, { recursive: true, force: true });
18945
19021
  info("-- Cleared generated directory");
@@ -18948,12 +19024,6 @@ async function clearGeneratedDirectory(workspaceRoot) {
18948
19024
  }
18949
19025
  }
18950
19026
  }
18951
- function getSqlDirectory(workspaceRoot) {
18952
- if (!workspaceRoot) {
18953
- workspaceRoot = getWorkspaceRoot();
18954
- }
18955
- return join9(workspaceRoot, "database", "migrations");
18956
- }
18957
19027
  async function createMigrationsTable(qb, dialect) {
18958
19028
  const driver = getDialectDriver(dialect);
18959
19029
  const createTableSql = driver.createMigrationsTable();
@@ -18992,6 +19062,7 @@ var init_migrate = __esm(() => {
18992
19062
  init_drivers();
18993
19063
  init_src2();
18994
19064
  init_introspect_db();
19065
+ init_workspace();
18995
19066
  });
18996
19067
 
18997
19068
  // src/actions/migrate-generate.ts
@@ -19003,28 +19074,49 @@ var init_migrate_generate = __esm(() => {
19003
19074
  });
19004
19075
 
19005
19076
  // src/actions/migrate-rollback.ts
19006
- import { existsSync as existsSync17, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19007
- import { dirname as dirname8, join as join10 } from "path";
19008
- import process26 from "process";
19077
+ import { existsSync as existsSync18, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19078
+ import { join as join11 } from "path";
19009
19079
  function splitSqlStatements2(sql) {
19010
19080
  const out = [];
19011
19081
  let buf = "";
19012
- let inString = false;
19013
- const lines = sql.split(`
19014
- `).filter((l) => !/^\s*--/.test(l));
19015
- const text = lines.join(`
19016
- `);
19017
- for (let i = 0;i < text.length; i++) {
19018
- const ch = text[i];
19019
- if (ch === "'")
19020
- inString = !inString;
19021
- if (ch === ";" && !inString) {
19082
+ let quote = null;
19083
+ for (let i = 0;i < sql.length; i++) {
19084
+ const ch = sql[i];
19085
+ if (quote) {
19086
+ buf += ch;
19087
+ if (ch === quote) {
19088
+ if (sql[i + 1] === quote) {
19089
+ buf += sql[i + 1];
19090
+ i++;
19091
+ } else {
19092
+ quote = null;
19093
+ }
19094
+ }
19095
+ continue;
19096
+ }
19097
+ if (ch === "'" || ch === '"' || ch === "`") {
19098
+ quote = ch;
19099
+ buf += ch;
19100
+ continue;
19101
+ }
19102
+ if (ch === "-" && sql[i + 1] === "-") {
19103
+ const newline = sql.indexOf(`
19104
+ `, i);
19105
+ i = newline === -1 ? sql.length : newline;
19106
+ continue;
19107
+ }
19108
+ if (ch === "/" && sql[i + 1] === "*") {
19109
+ const close = sql.indexOf("*/", i + 2);
19110
+ i = close === -1 ? sql.length : close + 1;
19111
+ continue;
19112
+ }
19113
+ if (ch === ";") {
19022
19114
  if (buf.trim())
19023
19115
  out.push(buf.trim());
19024
19116
  buf = "";
19025
- } else {
19026
- buf += ch;
19117
+ continue;
19027
19118
  }
19119
+ buf += ch;
19028
19120
  }
19029
19121
  if (buf.trim())
19030
19122
  out.push(buf.trim());
@@ -19038,8 +19130,20 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19038
19130
  let m;
19039
19131
  if (m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19040
19132
  down.push(`DROP TABLE IF EXISTS ${q(m[1])}`);
19041
- } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19042
- down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(m[2])}`);
19133
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+CONSTRAINT\s+["`']?(\w+)["`']?([\s\S]*)$/i.exec(stmt)) {
19134
+ if (dialect === "sqlite") {
19135
+ skipped.push(stmt);
19136
+ } else if (isMysqlLike(dialect) && /\bFOREIGN\s+KEY\b/i.test(m[3] ?? "")) {
19137
+ down.push(`ALTER TABLE ${q(m[1])} DROP FOREIGN KEY ${q(m[2])}`);
19138
+ } else {
19139
+ down.push(`ALTER TABLE ${q(m[1])} DROP CONSTRAINT ${q(m[2])}`);
19140
+ }
19141
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+["`']?(\w+)["`']?|["`'](\w+)["`']|(\w+))/i.exec(stmt)) {
19142
+ const column = m[2] ?? m[3] ?? m[4];
19143
+ if (m[4] && /^(?:PRIMARY|UNIQUE|INDEX|KEY|FOREIGN|CHECK|FULLTEXT|SPATIAL)$/i.test(m[4]))
19144
+ skipped.push(stmt);
19145
+ else
19146
+ down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(column)}`);
19043
19147
  } else if (m = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?(?:\s+ON\s+["`']?(\w+)["`']?)?/i.exec(stmt)) {
19044
19148
  down.push(isMysqlLike(dialect) && m[2] ? `DROP INDEX ${q(m[1])} ON ${q(m[2])}` : `DROP INDEX IF EXISTS ${q(m[1])}`);
19045
19149
  } else {
@@ -19048,22 +19152,6 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19048
19152
  }
19049
19153
  return { down: down.reverse(), skipped };
19050
19154
  }
19051
- function findWorkspaceRoot2(startPath) {
19052
- let currentPath = startPath;
19053
- while (currentPath !== dirname8(currentPath)) {
19054
- if (existsSync17(join10(currentPath, "package.json"))) {
19055
- return currentPath;
19056
- }
19057
- currentPath = dirname8(currentPath);
19058
- }
19059
- return process26.cwd();
19060
- }
19061
- function getSqlDirectory2(workspaceRoot) {
19062
- if (!workspaceRoot) {
19063
- workspaceRoot = findWorkspaceRoot2(process26.cwd());
19064
- }
19065
- return join10(workspaceRoot, "database", "migrations");
19066
- }
19067
19155
  async function migrateRollback(options = {}) {
19068
19156
  const steps = options.steps || 1;
19069
19157
  const reverseSchema = options.reverseSchema !== false;
@@ -19096,12 +19184,12 @@ async function migrateRollback(options = {}) {
19096
19184
  console.log(` - ${migration.migration}`);
19097
19185
  }
19098
19186
  console.log();
19099
- const sqlDir = getSqlDirectory2();
19187
+ const sqlDir = getSqlDirectory();
19100
19188
  let reversedAny = false;
19101
19189
  for (const migration of migrationsToRollback) {
19102
19190
  try {
19103
- const filePath = join10(sqlDir, migration.migration);
19104
- if (reverseSchema && existsSync17(filePath)) {
19191
+ const filePath = join11(sqlDir, migration.migration);
19192
+ if (reverseSchema && existsSync18(filePath)) {
19105
19193
  const forwardSql = readFileSync4(filePath, "utf8");
19106
19194
  const { down, skipped } = deriveDownStatements(forwardSql);
19107
19195
  for (const stmt of down) {
@@ -19112,10 +19200,10 @@ async function migrateRollback(options = {}) {
19112
19200
  if (skipped.length > 0)
19113
19201
  console.log(`-- \u26A0\uFE0F ${skipped.length} statement(s) in ${migration.migration} could not be auto-reversed (data/complex DDL) \u2014 reverse manually.`);
19114
19202
  }
19115
- const deleteSql = `DELETE FROM migrations WHERE migration = $1`;
19203
+ const deleteSql = `DELETE FROM migrations WHERE migration = ${getPlaceholder(1)}`;
19116
19204
  await qb.unsafe(deleteSql, [migration.migration]);
19117
19205
  console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
19118
- if (existsSync17(filePath)) {
19206
+ if (options.deleteFiles && existsSync18(filePath)) {
19119
19207
  unlinkSync2(filePath);
19120
19208
  console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
19121
19209
  }
@@ -19138,37 +19226,20 @@ async function migrateRollback(options = {}) {
19138
19226
  var init_migrate_rollback = __esm(() => {
19139
19227
  init_config();
19140
19228
  init_src2();
19229
+ init_workspace();
19141
19230
  });
19142
19231
 
19143
19232
  // src/actions/migrate-status.ts
19144
- import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
19145
- import { dirname as dirname9, join as join11 } from "path";
19146
- import process27 from "process";
19147
- function findWorkspaceRoot3(startPath) {
19148
- let currentPath = startPath;
19149
- while (currentPath !== dirname9(currentPath)) {
19150
- if (existsSync18(join11(currentPath, "package.json"))) {
19151
- return currentPath;
19152
- }
19153
- currentPath = dirname9(currentPath);
19154
- }
19155
- return process27.cwd();
19156
- }
19157
- function getSqlDirectory3(workspaceRoot) {
19158
- if (!workspaceRoot) {
19159
- workspaceRoot = findWorkspaceRoot3(process27.cwd());
19160
- }
19161
- return join11(workspaceRoot, "database", "migrations");
19162
- }
19233
+ import { existsSync as existsSync19, readdirSync as readdirSync8 } from "fs";
19163
19234
  async function migrateStatus() {
19164
19235
  const dialect = config5.dialect || "postgres";
19165
19236
  const driver = getDialectDriver(dialect);
19166
- const sqlDir = getSqlDirectory3();
19237
+ const sqlDir = getSqlDirectory();
19167
19238
  console.log("-- Migration Status");
19168
19239
  console.log(`-- Dialect: ${dialect}`);
19169
19240
  console.log(`-- SQL directory: ${sqlDir}`);
19170
19241
  console.log();
19171
- if (!existsSync18(sqlDir)) {
19242
+ if (!existsSync19(sqlDir)) {
19172
19243
  console.log("-- No SQL directory found. No migrations have been created yet.");
19173
19244
  return [];
19174
19245
  }
@@ -19189,27 +19260,16 @@ async function migrateStatus() {
19189
19260
  console.log();
19190
19261
  }
19191
19262
  const executedMap = new Map(executedMigrations.map((m) => [m.migration, m.executed_at]));
19192
- const statuses = migrationFiles.map((file2) => {
19193
- if (file2.includes("alter-") && file2.includes("-table")) {
19194
- return {
19195
- file: file2,
19196
- status: "transient"
19197
- };
19198
- }
19199
- const executedAt = executedMap.get(file2);
19200
- return {
19201
- file: file2,
19202
- status: executedAt ? "executed" : "pending",
19203
- executedAt
19204
- };
19205
- });
19263
+ const statuses = migrationFiles.map((file2) => ({
19264
+ file: file2,
19265
+ status: executedMap.has(file2) ? "executed" : "pending",
19266
+ executedAt: executedMap.get(file2)
19267
+ }));
19206
19268
  const executed = statuses.filter((s) => s.status === "executed");
19207
19269
  const pending = statuses.filter((s) => s.status === "pending");
19208
- const transient = statuses.filter((s) => s.status === "transient");
19209
19270
  console.log(`-- Total migrations: ${migrationFiles.length}`);
19210
19271
  console.log(`-- Executed: ${executed.length}`);
19211
19272
  console.log(`-- Pending: ${pending.length}`);
19212
- console.log(`-- Transient: ${transient.length}`);
19213
19273
  console.log();
19214
19274
  if (executed.length > 0) {
19215
19275
  console.log("\u2713 Executed Migrations:");
@@ -19225,13 +19285,6 @@ async function migrateStatus() {
19225
19285
  }
19226
19286
  console.log();
19227
19287
  }
19228
- if (transient.length > 0) {
19229
- console.log("\u26A1 Transient Migrations (ALTER TABLE - not tracked):");
19230
- for (const migration of transient) {
19231
- console.log(` - ${migration.file}`);
19232
- }
19233
- console.log();
19234
- }
19235
19288
  return statuses;
19236
19289
  } catch (err) {
19237
19290
  console.error("-- Failed to get migration status:", err);
@@ -19245,14 +19298,15 @@ var init_migrate_status = __esm(() => {
19245
19298
  init_config();
19246
19299
  init_drivers();
19247
19300
  init_src2();
19301
+ init_workspace();
19248
19302
  });
19249
19303
 
19250
19304
  // src/actions/model-show.ts
19251
19305
  import { readdirSync as readdirSync9 } from "fs";
19252
19306
  import { extname as extname2, join as join12 } from "path";
19253
- import process28 from "process";
19307
+ import process26 from "process";
19254
19308
  async function modelShow(modelName, options = {}) {
19255
- const dir = options.dir || join12(process28.cwd(), "app/Models");
19309
+ const dir = options.dir || join12(process26.cwd(), "app/Models");
19256
19310
  try {
19257
19311
  const files = readdirSync9(dir);
19258
19312
  const modelFile = files.find((f) => {
@@ -19466,9 +19520,9 @@ var init_query_explain_all = __esm(() => {
19466
19520
  // src/actions/relation-diagram.ts
19467
19521
  import { writeFileSync as writeFileSync12 } from "fs";
19468
19522
  import { join as join14 } from "path";
19469
- import process29 from "process";
19523
+ import process27 from "process";
19470
19524
  async function relationDiagram(options = {}) {
19471
- const dir = options.dir || join14(process29.cwd(), "app/Models");
19525
+ const dir = options.dir || join14(process27.cwd(), "app/Models");
19472
19526
  const format = options.format || "mermaid";
19473
19527
  try {
19474
19528
  const models = await loadModels({ modelsDir: dir });
@@ -19588,25 +19642,25 @@ var init_relation_diagram = __esm(() => {
19588
19642
  });
19589
19643
 
19590
19644
  // src/actions/seed.ts
19591
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19645
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19592
19646
  import { dirname as dirname10, join as join15 } from "path";
19593
- import process30 from "process";
19594
- function findWorkspaceRoot4(startPath) {
19647
+ import process28 from "process";
19648
+ function findWorkspaceRoot3(startPath) {
19595
19649
  let currentPath = startPath;
19596
19650
  while (currentPath !== dirname10(currentPath)) {
19597
- if (existsSync19(join15(currentPath, "package.json"))) {
19651
+ if (existsSync20(join15(currentPath, "package.json"))) {
19598
19652
  return currentPath;
19599
19653
  }
19600
19654
  currentPath = dirname10(currentPath);
19601
19655
  }
19602
- return process30.cwd();
19656
+ return process28.cwd();
19603
19657
  }
19604
19658
  async function loadSeeders(seedersDir) {
19605
- if (!existsSync19(seedersDir)) {
19659
+ if (!existsSync20(seedersDir)) {
19606
19660
  console.log(`-- Seeders directory not found: ${seedersDir}`);
19607
19661
  return [];
19608
19662
  }
19609
- const files = readdirSync11(seedersDir);
19663
+ const files = readdirSync11(seedersDir).sort((a, b) => a.localeCompare(b));
19610
19664
  const seederFiles = files.filter((file2) => (file2.endsWith(".ts") || file2.endsWith(".js")) && file2 !== "index.ts" && file2 !== "index.js");
19611
19665
  const seeders = [];
19612
19666
  for (const file2 of seederFiles) {
@@ -19619,6 +19673,7 @@ async function loadSeeders(seedersDir) {
19619
19673
  if (instance && typeof instance.run === "function") {
19620
19674
  seeders.push({
19621
19675
  name: file2.replace(/\.(ts|js)$/, ""),
19676
+ className: String(SeederClass.name || ""),
19622
19677
  instance
19623
19678
  });
19624
19679
  }
@@ -19631,7 +19686,7 @@ async function loadSeeders(seedersDir) {
19631
19686
  return seeders;
19632
19687
  }
19633
19688
  async function runSeeders(config6 = {}) {
19634
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19689
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19635
19690
  const seedersDir = config6.seedersDir || join15(workspaceRoot, "database/seeders");
19636
19691
  const verbose = config6.verbose ?? true;
19637
19692
  if (verbose) {
@@ -19666,17 +19721,18 @@ async function runSeeders(config6 = {}) {
19666
19721
  }
19667
19722
  }
19668
19723
  async function runSeeder(className, options = {}) {
19669
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19724
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19670
19725
  const seedersDir = join15(workspaceRoot, "database/seeders");
19671
19726
  const verbose = options.verbose ?? true;
19672
19727
  if (verbose) {
19673
19728
  console.log(`-- Running seeder: ${className}`);
19674
19729
  }
19675
19730
  const seeders = await loadSeeders(seedersDir);
19676
- const seeder = seeders.find((s) => s.name === className);
19731
+ const seeder = seeders.find((s) => s.className === className) ?? seeders.find((s) => s.name === className);
19677
19732
  if (!seeder) {
19678
- console.error(`-- Seeder not found: ${className}`);
19679
- throw new Error(`Seeder not found: ${className}`);
19733
+ const known = seeders.map((s) => s.className || s.name).join(", ") || "none";
19734
+ console.error(`-- Seeder not found: ${className}. Available: ${known}`);
19735
+ throw new Error(`Seeder not found: ${className}. Available: ${known}`);
19680
19736
  }
19681
19737
  const qb = createQueryBuilder();
19682
19738
  try {
@@ -19690,9 +19746,9 @@ async function runSeeder(className, options = {}) {
19690
19746
  }
19691
19747
  }
19692
19748
  async function makeSeeder(name) {
19693
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19749
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19694
19750
  const seedersDir = join15(workspaceRoot, "database/seeders");
19695
- if (!existsSync19(seedersDir)) {
19751
+ if (!existsSync20(seedersDir)) {
19696
19752
  mkdirSync9(seedersDir, { recursive: true });
19697
19753
  console.log(`-- Created seeders directory: ${seedersDir}`);
19698
19754
  }
@@ -19700,7 +19756,7 @@ async function makeSeeder(name) {
19700
19756
  const className = `${baseName}Seeder`;
19701
19757
  const fileName = `${className}.ts`;
19702
19758
  const filePath = join15(seedersDir, fileName);
19703
- if (existsSync19(filePath)) {
19759
+ if (existsSync20(filePath)) {
19704
19760
  console.error(`-- Seeder already exists: ${filePath}`);
19705
19761
  throw new Error(`Seeder already exists: ${filePath}`);
19706
19762
  }
@@ -19754,7 +19810,7 @@ export default class ${className} extends Seeder {
19754
19810
  console.log(`-- \u2713 Created seeder: ${filePath}`);
19755
19811
  }
19756
19812
  async function freshDatabase(options = {}) {
19757
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19813
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19758
19814
  const modelsDir = options.modelsDir || join15(workspaceRoot, "app/Models");
19759
19815
  const seedersDir = options.seedersDir || join15(workspaceRoot, "database/seeders");
19760
19816
  const verbose = options.verbose ?? true;
@@ -19814,22 +19870,22 @@ var init_unsafe = __esm(() => {
19814
19870
  });
19815
19871
 
19816
19872
  // src/actions/validate.ts
19817
- import { existsSync as existsSync20 } from "fs";
19873
+ import { existsSync as existsSync21 } from "fs";
19818
19874
  import { dirname as dirname11, join as join16 } from "path";
19819
- import process31 from "process";
19820
- function findWorkspaceRoot5(startPath) {
19875
+ import process29 from "process";
19876
+ function findWorkspaceRoot4(startPath) {
19821
19877
  let currentPath = startPath;
19822
19878
  while (currentPath !== dirname11(currentPath)) {
19823
- if (existsSync20(join16(currentPath, "package.json"))) {
19879
+ if (existsSync21(join16(currentPath, "package.json"))) {
19824
19880
  return currentPath;
19825
19881
  }
19826
19882
  currentPath = dirname11(currentPath);
19827
19883
  }
19828
- return process31.cwd();
19884
+ return process29.cwd();
19829
19885
  }
19830
19886
  async function validateSchema(dir) {
19831
19887
  if (!dir) {
19832
- dir = join16(findWorkspaceRoot5(process31.cwd()), "app/Models");
19888
+ dir = join16(findWorkspaceRoot4(process29.cwd()), "app/Models");
19833
19889
  }
19834
19890
  const dialect = config5.dialect || "postgres";
19835
19891
  console.log("-- Validating Schema");
@@ -25080,18 +25136,11 @@ function toSnakeCase(str) {
25080
25136
  snakeCaseCache.set(str, cached);
25081
25137
  return cached;
25082
25138
  }
25083
- function toTableName(modelName) {
25139
+ function toTableName2(modelName) {
25084
25140
  let cached = tableNameCache.get(modelName);
25085
25141
  if (cached !== undefined)
25086
25142
  return cached;
25087
- const snake = toSnakeCase(modelName);
25088
- if (snake.endsWith("y") && !snake.endsWith("ay") && !snake.endsWith("ey") && !snake.endsWith("oy") && !snake.endsWith("uy")) {
25089
- cached = snake.slice(0, -1) + "ies";
25090
- } else if (snake.endsWith("s") || snake.endsWith("x") || snake.endsWith("ch") || snake.endsWith("sh")) {
25091
- cached = `${snake}es`;
25092
- } else {
25093
- cached = `${snake}s`;
25094
- }
25143
+ cached = toTableName(modelName);
25095
25144
  tableNameCache.set(modelName, cached);
25096
25145
  return cached;
25097
25146
  }
@@ -25176,14 +25225,14 @@ function resolveRelation(definition, relationName) {
25176
25225
  const hasManyModel = findModelName(definition.hasMany);
25177
25226
  if (hasManyModel) {
25178
25227
  const relatedModel = getModelFromRegistry(hasManyModel);
25179
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasManyModel);
25228
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasManyModel);
25180
25229
  const foreignKey = toSnakeCase(parentName) + "_id";
25181
25230
  return { type: "hasMany", relatedModelName: hasManyModel, relatedTable, foreignKey, localKey: parentPk };
25182
25231
  }
25183
25232
  const hasOneModel = findModelName(definition.hasOne);
25184
25233
  if (hasOneModel) {
25185
25234
  const relatedModel = getModelFromRegistry(hasOneModel);
25186
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasOneModel);
25235
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasOneModel);
25187
25236
  const foreignKey = toSnakeCase(parentName) + "_id";
25188
25237
  return { type: "hasOne", relatedModelName: hasOneModel, relatedTable, foreignKey, localKey: parentPk };
25189
25238
  }
@@ -25191,7 +25240,7 @@ function resolveRelation(definition, relationName) {
25191
25240
  if (belongsToEntry) {
25192
25241
  const belongsToModel = belongsToEntry.model;
25193
25242
  const relatedModel = getModelFromRegistry(belongsToModel);
25194
- const relatedTable = relatedModel?.getTable?.() || toTableName(belongsToModel);
25243
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(belongsToModel);
25195
25244
  const relatedPk = relatedModel?.getDefinition?.()?.primaryKey || "id";
25196
25245
  const foreignKey = belongsToEntry.foreignKey || `${toSnakeCase(belongsToModel)}_id`;
25197
25246
  return { type: "belongsTo", relatedModelName: belongsToModel, relatedTable, foreignKey, localKey: relatedPk };
@@ -25201,7 +25250,7 @@ function resolveRelation(definition, relationName) {
25201
25250
  const isConfig = typeof found.entry === "object";
25202
25251
  const relatedModelName = isConfig ? found.entry.model : found.entry;
25203
25252
  const relatedModel = getModelFromRegistry(relatedModelName);
25204
- const relatedTable = relatedModel?.getTable?.() || toTableName(relatedModelName);
25253
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(relatedModelName);
25205
25254
  const config6 = isConfig ? found.entry : null;
25206
25255
  let pivotTable;
25207
25256
  let pivotModelName;
@@ -25213,7 +25262,7 @@ function resolveRelation(definition, relationName) {
25213
25262
  if (!throughModel) {
25214
25263
  throw new Error(`[orm] belongsToMany relation '${relationName}' on '${parentName}' references unknown through model '${config6.through}'. Make sure '${config6.through}' is registered.`);
25215
25264
  }
25216
- pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName(config6.through);
25265
+ pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName2(config6.through);
25217
25266
  } else {
25218
25267
  const a2 = singularizeWord(parentTable);
25219
25268
  const b2 = singularizeWord(relatedTable);
@@ -25264,10 +25313,10 @@ function resolveRelation(definition, relationName) {
25264
25313
  return null;
25265
25314
  const throughModel = getModelFromRegistry(throughModelName);
25266
25315
  const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
25267
- const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
25316
+ const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName2(throughModelName);
25268
25317
  const throughPk = throughDef?.primaryKey || "id";
25269
25318
  const targetModel = getModelFromRegistry(targetModelName);
25270
- const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
25319
+ const targetTable = targetModel?.getTable?.() || toTableName2(targetModelName);
25271
25320
  return {
25272
25321
  type,
25273
25322
  relatedModelName: targetModelName,
@@ -27377,7 +27426,7 @@ function resolveKeyPattern(keyPatterns, data) {
27377
27426
  }
27378
27427
  return resolved;
27379
27428
  }
27380
- async function parseStacksModels(_config2) {
27429
+ async function parseStacksModels(_config) {
27381
27430
  return { models: new Map };
27382
27431
  }
27383
27432
  function toDynamoDBItem(item) {
@@ -27864,7 +27913,7 @@ function buildDatabaseSchema(models) {
27864
27913
  for (const modelName of Object.keys(models)) {
27865
27914
  const rawModel = models[modelName];
27866
27915
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27867
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
27916
+ const table = tableNameFor(m2);
27868
27917
  const attrs = m2.attributes ?? {};
27869
27918
  const columns = {};
27870
27919
  for (const key of Object.keys(attrs))
@@ -27876,30 +27925,53 @@ function buildDatabaseSchema(models) {
27876
27925
  }
27877
27926
  return schema;
27878
27927
  }
27928
+ var init_factory = () => {};
27879
27929
 
27880
27930
  // src/loader.ts
27881
- import { readdirSync as readdirSync12, statSync as statSync5 } from "fs";
27882
- import { basename, extname as extname4 } from "path";
27883
- import process33 from "process";
27884
- async function loadModels(options) {
27885
- const cwd = options.cwd ?? process33.cwd();
27886
- const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
27887
- const result = {};
27888
- const entries = readdirSync12(dir);
27931
+ import { readdirSync as readdirSync12 } from "fs";
27932
+ import { basename, extname as extname4, isAbsolute as isAbsolute3, join as join17 } from "path";
27933
+ import process30 from "process";
27934
+ function isModelFile(name) {
27935
+ const ext = extname4(name);
27936
+ if (!MODEL_EXTENSIONS.has(ext))
27937
+ return false;
27938
+ const base = basename(name, ext);
27939
+ if (base.startsWith("_") || base === "index")
27940
+ return false;
27941
+ return !base.endsWith(".d");
27942
+ }
27943
+ function isModelDefinition(def) {
27944
+ if (!def || typeof def !== "object")
27945
+ return false;
27946
+ const d2 = def;
27947
+ return typeof d2.name === "string" || typeof d2.table === "string";
27948
+ }
27949
+ function collectModelFiles(dir) {
27950
+ const files = [];
27951
+ const entries = readdirSync12(dir, { withFileTypes: true }).sort((a2, b2) => a2.name.localeCompare(b2.name));
27889
27952
  for (const entry of entries) {
27890
- const full = `${dir}/${entry}`;
27891
- const st = statSync5(full);
27892
- if (st.isDirectory())
27893
- continue;
27894
- const ext = extname4(full);
27895
- if (![".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"].includes(ext))
27953
+ const full = join17(dir, entry.name);
27954
+ if (entry.isDirectory()) {
27955
+ files.push(...collectModelFiles(full));
27896
27956
  continue;
27957
+ }
27958
+ if (isModelFile(entry.name))
27959
+ files.push(full);
27960
+ }
27961
+ return files;
27962
+ }
27963
+ async function loadModels(options) {
27964
+ const cwd = options.cwd ?? process30.cwd();
27965
+ const dir = isAbsolute3(options.modelsDir) ? options.modelsDir : join17(cwd, options.modelsDir);
27966
+ const result = {};
27967
+ for (const full of collectModelFiles(dir)) {
27897
27968
  const cacheBuster = `?t=${Date.now()}-${Math.random().toString(36).slice(2)}`;
27898
27969
  const mod = await import(`${full}${cacheBuster}`);
27899
27970
  const rawDef = mod.default ?? mod;
27900
27971
  const def = rawDef.definition ?? rawDef.getDefinition?.() ?? rawDef;
27901
- const fileName = basename(entry, ext);
27902
- const name = def.name ?? fileName;
27972
+ if (!isModelDefinition(def))
27973
+ continue;
27974
+ const name = def.name ?? basename(full, extname4(full));
27903
27975
  result[name] = {
27904
27976
  ...def,
27905
27977
  name
@@ -27907,7 +27979,10 @@ async function loadModels(options) {
27907
27979
  }
27908
27980
  return result;
27909
27981
  }
27910
- var init_loader = () => {};
27982
+ var MODEL_EXTENSIONS;
27983
+ var init_loader = __esm(() => {
27984
+ MODEL_EXTENSIONS = new Set([".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]);
27985
+ });
27911
27986
 
27912
27987
  // src/meta.ts
27913
27988
  function buildSchemaMeta(models) {
@@ -27919,7 +27994,7 @@ function buildSchemaMeta(models) {
27919
27994
  for (const name of Object.keys(models)) {
27920
27995
  const rawModel = models[name];
27921
27996
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27922
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
27997
+ const table = tableNameFor(m2);
27923
27998
  modelToTable[name] = table;
27924
27999
  tableToModel[table] = name;
27925
28000
  primaryKeys[table] = m2.primaryKey ?? "id";
@@ -27993,9 +28068,9 @@ function buildSchemaMeta(models) {
27993
28068
  var init_meta = () => {};
27994
28069
 
27995
28070
  // src/migrations.ts
27996
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync14 } from "fs";
27997
- import { dirname as dirname12, join as join17 } from "path";
27998
- import process35 from "process";
28071
+ import { existsSync as existsSync23, mkdirSync as mkdirSync10, readdirSync as readdirSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync14 } from "fs";
28072
+ import { dirname as dirname12, join as join18 } from "path";
28073
+ import process31 from "process";
27999
28074
  function info2(message) {
28000
28075
  if (config5.verbose)
28001
28076
  console.log(message);
@@ -28006,36 +28081,80 @@ function snakeCase(str) {
28006
28081
  function singularizeTable(table) {
28007
28082
  return singularizerFor(config5.relations?.singularizeStrategy)(table);
28008
28083
  }
28009
- function findWorkspaceRoot6(startPath) {
28084
+ function lastCreatedMigrationFiles() {
28085
+ return [...createdMigrationFiles];
28086
+ }
28087
+ function findWorkspaceRoot5(startPath) {
28010
28088
  let currentPath = startPath;
28011
28089
  while (currentPath !== dirname12(currentPath)) {
28012
- if (existsSync21(join17(currentPath, "package.json"))) {
28090
+ if (existsSync23(join18(currentPath, "package.json"))) {
28013
28091
  return currentPath;
28014
28092
  }
28015
28093
  currentPath = dirname12(currentPath);
28016
28094
  }
28017
- return process35.cwd();
28095
+ return process31.cwd();
28018
28096
  }
28019
28097
  function ensureSqlDirectory2() {
28020
- const workspaceRoot = findWorkspaceRoot6(process35.cwd());
28021
- const sqlDir = join17(workspaceRoot, "database", "migrations");
28022
- if (!existsSync21(sqlDir)) {
28098
+ const workspaceRoot = findWorkspaceRoot5(process31.cwd());
28099
+ const sqlDir = join18(workspaceRoot, "database", "migrations");
28100
+ if (!existsSync23(sqlDir)) {
28023
28101
  mkdirSync10(sqlDir, { recursive: true });
28024
28102
  info2(`-- Created SQL directory: ${sqlDir}`);
28025
28103
  }
28026
28104
  return sqlDir;
28027
28105
  }
28106
+ function isGeneratedMigrationSql(sql2) {
28107
+ return sql2.trimStart().startsWith(GENERATED_MARKER);
28108
+ }
28109
+ function migrationBody(sql2) {
28110
+ return sql2.split(`
28111
+ `).filter((line) => !line.trimStart().startsWith("--")).join(`
28112
+ `).replace(/\s+/g, " ").trim().replace(/;+$/, "");
28113
+ }
28114
+ function existingMigrationBodies(sqlDir) {
28115
+ const bodies = new Set;
28116
+ let files;
28117
+ try {
28118
+ files = readdirSync13(sqlDir).filter((f2) => f2.endsWith(".sql"));
28119
+ } catch {
28120
+ return bodies;
28121
+ }
28122
+ for (const file2 of files) {
28123
+ try {
28124
+ bodies.add(migrationBody(readFileSync6(join18(sqlDir, file2), "utf8")));
28125
+ } catch {}
28126
+ }
28127
+ return bodies;
28128
+ }
28028
28129
  function createMigrationFile(statement, fileName) {
28029
28130
  if (!statement)
28030
28131
  return false;
28031
28132
  const sqlDir = ensureSqlDirectory2();
28133
+ if (existingCorpusBodies === null)
28134
+ existingCorpusBodies = existingMigrationBodies(sqlDir);
28135
+ const body = migrationBody(statement);
28136
+ if (body.length > 0 && existingCorpusBodies.has(body)) {
28137
+ info2(`-- Migration already in the corpus, not writing it again: ${fileName}`);
28138
+ return false;
28139
+ }
28032
28140
  migrationCounter++;
28033
- const sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28034
- const fullFileName = `${sequence}-${fileName}.sql`;
28035
- const filePath = join17(sqlDir, fullFileName);
28036
- writeFileSync14(filePath, statement);
28141
+ let sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28142
+ let fullFileName = `${sequence}-${fileName}.sql`;
28143
+ if (!useDeterministicNames) {
28144
+ let next = Number(sequence);
28145
+ while (existsSync23(join18(sqlDir, fullFileName))) {
28146
+ next += 1;
28147
+ sequence = String(next);
28148
+ fullFileName = `${sequence}-${fileName}.sql`;
28149
+ }
28150
+ }
28151
+ const filePath = join18(sqlDir, fullFileName);
28152
+ writeFileSync14(filePath, `${GENERATED_MARKER}
28153
+ ${statement}`);
28037
28154
  info2(`-- Migration file created: ${fullFileName}`);
28038
28155
  migrationsCreatedCount++;
28156
+ createdMigrationFiles.push(fullFileName);
28157
+ existingCorpusBodies?.add(body);
28039
28158
  return true;
28040
28159
  }
28041
28160
  function guessTypeFromName(columnName) {
@@ -28210,7 +28329,7 @@ function buildMigrationPlan2(models, options) {
28210
28329
  const tables = [];
28211
28330
  for (const modelName of Object.keys(models)) {
28212
28331
  const model = models[modelName];
28213
- const table = model.table || `${String(model.name).toLowerCase()}s`;
28332
+ const table = tableNameFor(model);
28214
28333
  const primaryKey = model.primaryKey ?? "id";
28215
28334
  const _autoIncrement = model.autoIncrement !== false;
28216
28335
  const attrs = model.attributes ?? {};
@@ -28302,9 +28421,7 @@ function buildMigrationPlan2(models, options) {
28302
28421
  if (columns.some((c2) => c2.name === fkColumnName))
28303
28422
  continue;
28304
28423
  const refTable = meta.modelToTable[rel.model];
28305
- if (!refTable)
28306
- continue;
28307
- const refPk = meta.primaryKeys[refTable] ?? "id";
28424
+ const refPk = refTable ? meta.primaryKeys[refTable] ?? "id" : undefined;
28308
28425
  columns.push({
28309
28426
  name: fkColumnName,
28310
28427
  type: "bigint",
@@ -28312,7 +28429,7 @@ function buildMigrationPlan2(models, options) {
28312
28429
  isUnique: false,
28313
28430
  isNullable: true,
28314
28431
  hasDefault: false,
28315
- references: { table: refTable, column: refPk, onDelete: rel.onDelete }
28432
+ references: refTable && refPk ? { table: refTable, column: refPk, onDelete: rel.onDelete } : undefined
28316
28433
  });
28317
28434
  }
28318
28435
  const traits = model.traits;
@@ -28393,7 +28510,7 @@ function buildMigrationPlan2(models, options) {
28393
28510
  for (const modelName of Object.keys(models)) {
28394
28511
  const model = models[modelName];
28395
28512
  const traits = model?.traits ?? {};
28396
- const modelTable = model.table || `${String(model.name).toLowerCase()}s`;
28513
+ const modelTable = tableNameFor(model);
28397
28514
  const singular = singularizeTable(modelTable);
28398
28515
  if (traits.likeable) {
28399
28516
  const opts = typeof traits.likeable === "object" ? traits.likeable : {};
@@ -28460,7 +28577,7 @@ function buildMigrationPlan2(models, options) {
28460
28577
  const btm = model?.belongsToMany;
28461
28578
  if (!btm || typeof btm !== "object" || Array.isArray(btm))
28462
28579
  continue;
28463
- const parentTable = model.table || `${String(model.name).toLowerCase()}s`;
28580
+ const parentTable = tableNameFor(model);
28464
28581
  const parentSingular = singularizeTable(parentTable);
28465
28582
  for (const [relKey, value] of Object.entries(btm)) {
28466
28583
  const cfg = value;
@@ -28521,6 +28638,8 @@ function generateSql(plan, opts = {}) {
28521
28638
  migrationCounter = 0;
28522
28639
  migrationsCreatedCount = 0;
28523
28640
  migrationsUpdatedCount = 0;
28641
+ createdMigrationFiles = [];
28642
+ existingCorpusBodies = null;
28524
28643
  useDeterministicNames = true;
28525
28644
  const emit = opts.dryRun ? () => false : createMigrationFile;
28526
28645
  const statements = [];
@@ -28782,12 +28901,13 @@ function referencesAreDifferent(r1, r2) {
28782
28901
  const act = (a2) => (a2 ?? "no action").toLowerCase();
28783
28902
  return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
28784
28903
  }
28904
+ function indexKey(index) {
28905
+ return `${index.type}:${index.name}:${index.columns.join(",")}`;
28906
+ }
28785
28907
  function mapIndexesByKey(indexes) {
28786
28908
  const map = {};
28787
- for (const i2 of indexes) {
28788
- const key = `${i2.type}:${i2.name}:${i2.columns.join(",")}`;
28789
- map[key] = i2;
28790
- }
28909
+ for (const i2 of indexes)
28910
+ map[indexKey(i2)] = i2;
28791
28911
  return map;
28792
28912
  }
28793
28913
  function generateDiffOperations(previous, next, opts = {}) {
@@ -28802,10 +28922,13 @@ function generateDiffOperations(previous, next, opts = {}) {
28802
28922
  return { statements: statements2, operations: operations2 };
28803
28923
  }
28804
28924
  const applyRenames = opts.applyRenames !== false;
28925
+ const preserveUnknownColumns = opts.preserveUnknownColumns === true;
28805
28926
  const emit = opts.dryRun ? () => false : createMigrationFile;
28806
28927
  migrationCounter = 0;
28807
28928
  migrationsCreatedCount = 0;
28808
28929
  migrationsUpdatedCount = 0;
28930
+ createdMigrationFiles = [];
28931
+ existingCorpusBodies = null;
28809
28932
  useDeterministicNames = false;
28810
28933
  const chunks = [];
28811
28934
  const operations = [];
@@ -28904,22 +29027,32 @@ function generateDiffOperations(previous, next, opts = {}) {
28904
29027
  }
28905
29028
  for (const tableName of Object.keys(nextTables)) {
28906
29029
  const prev = prevTables[tableName];
28907
- const curr = nextTables[tableName];
29030
+ let curr = nextTables[tableName];
28908
29031
  if (!prev)
28909
29032
  continue;
28910
29033
  const prevCols = mapColumnsByName(prev.columns);
28911
- const currCols = mapColumnsByName(curr.columns);
28912
29034
  const prevIdx = mapIndexesByKey(prev.indexes);
28913
- const currIdx = mapIndexesByKey(curr.indexes);
29035
+ let currCols = mapColumnsByName(curr.columns);
29036
+ let currIdx = mapIndexesByKey(curr.indexes);
28914
29037
  let renames = [];
28915
29038
  let removedCols = Object.keys(prevCols).filter((n2) => !currCols[n2]);
28916
29039
  let addedCols = Object.keys(currCols).filter((n2) => !prevCols[n2]);
28917
- if (applyRenames) {
29040
+ if (applyRenames && !preserveUnknownColumns) {
28918
29041
  const detected = detectColumnRenames(prevCols, currCols, dialect);
28919
29042
  renames = detected.renames;
28920
29043
  removedCols = detected.removed;
28921
29044
  addedCols = detected.added;
28922
29045
  }
29046
+ if (preserveUnknownColumns && removedCols.length > 0) {
29047
+ const preserved = removedCols.map((name) => prevCols[name]);
29048
+ const preservedNames = new Set(removedCols);
29049
+ const keptIndexes = prev.indexes.filter((idx) => idx.columns.some((c2) => preservedNames.has(c2)) && !currIdx[indexKey(idx)]);
29050
+ 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`);
29051
+ curr = { ...curr, columns: [...curr.columns, ...preserved], indexes: [...curr.indexes, ...keptIndexes] };
29052
+ currCols = mapColumnsByName(curr.columns);
29053
+ currIdx = mapIndexesByKey(curr.indexes);
29054
+ removedCols = [];
29055
+ }
28923
29056
  const modifiedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && columnsAreDifferent(prevCols[n2], currCols[n2], dialect));
28924
29057
  const fkChangedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && referencesAreDifferent(prevCols[n2].references, currCols[n2].references));
28925
29058
  if (dialect === "sqlite") {
@@ -29067,11 +29200,12 @@ function generateDiffOperations(previous, next, opts = {}) {
29067
29200
  function generateDiffSql(previous, next, opts = {}) {
29068
29201
  return generateDiffOperations(previous, next, opts).statements;
29069
29202
  }
29070
- var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true;
29203
+ var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true, createdMigrationFiles, existingCorpusBodies = null, GENERATED_MARKER = "-- qb:generated";
29071
29204
  var init_migrations = __esm(() => {
29072
29205
  init_config();
29073
29206
  init_drivers();
29074
29207
  init_meta();
29208
+ createdMigrationFiles = [];
29075
29209
  });
29076
29210
 
29077
29211
  // src/schema.ts
@@ -29105,6 +29239,7 @@ var init_src2 = __esm(() => {
29105
29239
  init_dynamodb_single_table();
29106
29240
  init_dynamodb_tooling_adapter();
29107
29241
  init_dynamodb();
29242
+ init_factory();
29108
29243
  init_loader();
29109
29244
  init_meta();
29110
29245
  init_migrations();
@@ -29120,9 +29255,11 @@ export {
29120
29255
  validateSchema,
29121
29256
  unsafe,
29122
29257
  unmarshallItem,
29258
+ toTableName,
29123
29259
  toModelInstance,
29124
29260
  toDynamoDBItem,
29125
29261
  tinker,
29262
+ tableNameFor,
29126
29263
  tableInfo,
29127
29264
  stripTrailingS,
29128
29265
  startConsole,
@@ -29141,6 +29278,7 @@ export {
29141
29278
  resolveSqlitePragmas,
29142
29279
  resolvePivot,
29143
29280
  resolveKeyPattern,
29281
+ resolveDialect,
29144
29282
  resetDatabase,
29145
29283
  resetConnection,
29146
29284
  relationDiagram,
@@ -29153,6 +29291,7 @@ export {
29153
29291
  dbOptimize as optimizeDatabase,
29154
29292
  modelShow,
29155
29293
  modelNameForTable,
29294
+ migrationBody,
29156
29295
  migrateStatus,
29157
29296
  migrateRollback,
29158
29297
  migrateModels,
@@ -29162,8 +29301,10 @@ export {
29162
29301
  makeSeeder,
29163
29302
  makeModel,
29164
29303
  loadModels,
29304
+ lastCreatedMigrationFiles,
29165
29305
  iterateAllPivots,
29166
29306
  isMysqlLike,
29307
+ isGeneratedMigrationSql,
29167
29308
  isDefinitionEqual,
29168
29309
  isBrowser,
29169
29310
  introspectDatabase,
@@ -29258,6 +29399,7 @@ export {
29258
29399
  MySQLDriver,
29259
29400
  Model,
29260
29401
  MIGRATIONS_TABLE,
29402
+ GENERATED_MARKER,
29261
29403
  EntityQueryBuilder,
29262
29404
  DynamoDBToolingAdapter,
29263
29405
  DynamoDBQueryBuilder,