bun-query-builder 0.1.63 → 0.2.2

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}`;
@@ -11801,7 +11810,7 @@ var {SQL } = globalThis.Bun;
11801
11810
  import { Database } from "bun:sqlite";
11802
11811
  import { mkdirSync as mkdirSync5 } from "fs";
11803
11812
  import { dirname as dirname5 } from "path";
11804
- import process19 from "process";
11813
+ import process21 from "process";
11805
11814
 
11806
11815
  class SQLiteWrapper {
11807
11816
  db;
@@ -12079,9 +12088,13 @@ function sqliteFileFromUrl(url) {
12079
12088
  const path = stripped.startsWith("///") ? stripped.slice(2) : stripped.startsWith("//") ? stripped.slice(2) : stripped;
12080
12089
  return path || ":memory:";
12081
12090
  }
12091
+ function resolveDatabaseName(dbConfig = config5.database) {
12092
+ const raw = process21.env.DB_DATABASE || dbConfig?.database || "";
12093
+ return String(raw).replace(/^['"]|['"]$/g, "");
12094
+ }
12082
12095
  function createConnectionString(dialect, dbConfig) {
12083
- if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process19.env.DB_CONNECTION === dialect) {
12084
- const e = process19.env;
12096
+ if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process21.env.DB_CONNECTION === dialect) {
12097
+ const e = process21.env;
12085
12098
  const envDb = (e.DB_DATABASE || dbConfig.database || "").replace(/^['"]|['"]$/g, "");
12086
12099
  const envUser = e.DB_USERNAME || dbConfig.username;
12087
12100
  const envPass = e.DB_PASSWORD ?? dbConfig.password ?? "";
@@ -12095,7 +12108,7 @@ function createConnectionString(dialect, dbConfig) {
12095
12108
  return dialect === "sqlite" ? sqliteFileFromUrl(dbConfig.url) : dbConfig.url;
12096
12109
  }
12097
12110
  const { database, username, password, host = "localhost", port, ssl } = dbConfig;
12098
- 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" : "";
12099
12112
  switch (dialect) {
12100
12113
  case "postgres":
12101
12114
  return `postgres://${username}:${password}@${host}${port ? `:${port}` : ""}/${database}${sslQ}`;
@@ -12369,6 +12382,17 @@ var init_benchmark = __esm(() => {
12369
12382
  });
12370
12383
 
12371
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
+ }
12372
12396
  function stripTrailingS(name) {
12373
12397
  return name.endsWith("s") ? name.slice(0, -1) : name;
12374
12398
  }
@@ -12483,6 +12507,24 @@ var init_pivot = __esm(() => {
12483
12507
  function isRawExpression(expr) {
12484
12508
  return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
12485
12509
  }
12510
+ function isBoundSqlExpression(expr) {
12511
+ return typeof expr === "object" && expr !== null && "sql" in expr && typeof expr.sql === "string" && (!("parameters" in expr) || Array.isArray(expr.parameters));
12512
+ }
12513
+ function renderBoundSqlExpression(expression, startIndex) {
12514
+ const parameters = expression.parameters ?? [];
12515
+ if (config5.dialect !== "postgres" || parameters.length === 0)
12516
+ return { text: expression.sql, parameters };
12517
+ let parameterIndex = 0;
12518
+ const text = expression.sql.replace(/\?/g, (placeholder) => {
12519
+ if (parameterIndex >= parameters.length)
12520
+ return placeholder;
12521
+ return getPlaceholder(startIndex + parameterIndex++);
12522
+ });
12523
+ if (parameterIndex !== parameters.length) {
12524
+ throw new TypeError(`[query-builder] SQL expression declares ${parameters.length} parameters but contains only ${parameterIndex} placeholders`);
12525
+ }
12526
+ return { text, parameters };
12527
+ }
12486
12528
  function raw(strings, ...values) {
12487
12529
  if (typeof strings === "string")
12488
12530
  return { raw: strings };
@@ -12956,6 +12998,31 @@ function createQueryBuilder(state) {
12956
12998
  throw err;
12957
12999
  });
12958
13000
  }
13001
+ function mutationCount(result) {
13002
+ if (typeof result === "number")
13003
+ return Number.isFinite(result) ? result : 0;
13004
+ if (typeof result === "bigint")
13005
+ return Number(result);
13006
+ if (!result || typeof result !== "object")
13007
+ return 0;
13008
+ const record = result;
13009
+ for (const key of [
13010
+ "changes",
13011
+ "affectedRows",
13012
+ "count",
13013
+ "rowCount",
13014
+ "numAffectedRows",
13015
+ "numUpdatedRows",
13016
+ "numDeletedRows",
13017
+ "numInsertedOrUpdatedRows"
13018
+ ]) {
13019
+ if (record[key] !== undefined && record[key] !== null)
13020
+ return mutationCount(record[key]);
13021
+ }
13022
+ if (Array.isArray(result))
13023
+ return result.reduce((total, item) => total + mutationCount(item), 0);
13024
+ return 0;
13025
+ }
12959
13026
  function makeExecutableQuery(q, text) {
12960
13027
  const sqlText = text ?? computeSqlText(q);
12961
13028
  return {
@@ -14051,7 +14118,16 @@ function createQueryBuilder(state) {
14051
14118
  built = null;
14052
14119
  return this;
14053
14120
  }
14054
- return this;
14121
+ if (typeof expr === "function") {
14122
+ throw new TypeError("where(callback) is not supported. This builder has no expression callback, and " + "ignoring one silently would leave the query matching every row the filter was " + "meant to exclude. Use whereNested(subquery), whereAny(...), or the orWhere* " + "helpers to build a grouped condition.");
14123
+ }
14124
+ if (expr === undefined || expr === null) {
14125
+ throw new TypeError("where() was called with no condition.");
14126
+ }
14127
+ if (typeof expr === "string") {
14128
+ throw new TypeError(`where(${JSON.stringify(expr)}) is missing an operator and a value. Pass ` + "where(column, operator, value), or whereRaw() for a raw fragment.");
14129
+ }
14130
+ throw new TypeError(`where() does not understand a condition of type ${typeof expr}.`);
14055
14131
  },
14056
14132
  whereNull(column) {
14057
14133
  validateIdentifier(String(column), "whereNull(column)");
@@ -15656,8 +15732,17 @@ function createQueryBuilder(state) {
15656
15732
  const setClauses = Array.from({ length: len });
15657
15733
  for (let i = 0;i < len; i++) {
15658
15734
  const key = keys[i];
15659
- setClauses[i] = `${quoteId(key)} = ${getPlaceholder(i + 1)}`;
15660
- params.push(values[key]);
15735
+ const value = values[key];
15736
+ if (isRawExpression(value)) {
15737
+ setClauses[i] = `${quoteId(key)} = ${value.raw}`;
15738
+ } else if (isBoundSqlExpression(value)) {
15739
+ const expression = renderBoundSqlExpression(value, params.length + 1);
15740
+ setClauses[i] = `${quoteId(key)} = ${expression.text}`;
15741
+ params.push(...expression.parameters);
15742
+ } else {
15743
+ setClauses[i] = `${quoteId(key)} = ${getPlaceholder(params.length + 1)}`;
15744
+ params.push(value);
15745
+ }
15661
15746
  }
15662
15747
  sqlText = `${sqlText} SET ${setClauses.join(", ")}`;
15663
15748
  built = _sql.unsafe(sqlText, params);
@@ -15665,17 +15750,29 @@ function createQueryBuilder(state) {
15665
15750
  },
15666
15751
  where(expr, op, value) {
15667
15752
  const getWhereKeyword = () => SQL_PATTERNS.WHERE.test(sqlText) ? "AND" : "WHERE";
15668
- if (typeof expr === "string" && op !== undefined) {
15753
+ if (op !== undefined && (typeof expr === "string" || isRawExpression(expr) || isBoundSqlExpression(expr))) {
15754
+ const safeOperator = assertSafeWhereOperator(op, "updateTable.where");
15755
+ let left;
15756
+ if (typeof expr === "string") {
15757
+ left = quoteId(expr);
15758
+ } else if (isRawExpression(expr)) {
15759
+ left = expr.raw;
15760
+ } else {
15761
+ const expression = renderBoundSqlExpression(expr, params.length + 1);
15762
+ left = expression.text;
15763
+ params.push(...expression.parameters);
15764
+ }
15669
15765
  const paramIndex = params.length + 1;
15670
- sqlText = `${sqlText} ${getWhereKeyword()} ${quoteId(expr)} ${op} ${getPlaceholder(paramIndex)}`;
15766
+ sqlText = `${sqlText} ${getWhereKeyword()} ${left} ${safeOperator} ${getPlaceholder(paramIndex)}`;
15671
15767
  params.push(value);
15672
15768
  built = _sql.unsafe(sqlText, params);
15673
15769
  return this;
15674
15770
  }
15675
15771
  if (Array.isArray(expr)) {
15676
15772
  const [col, op2, val] = expr;
15773
+ const safeOperator = assertSafeWhereOperator(op2, "updateTable.where");
15677
15774
  const paramIndex = params.length + 1;
15678
- sqlText = `${sqlText} ${getWhereKeyword()} ${quoteId(String(col))} ${String(op2)} ${getPlaceholder(paramIndex)}`;
15775
+ sqlText = `${sqlText} ${getWhereKeyword()} ${quoteId(String(col))} ${safeOperator} ${getPlaceholder(paramIndex)}`;
15679
15776
  params.push(val);
15680
15777
  built = _sql.unsafe(sqlText, params);
15681
15778
  return this;
@@ -15734,15 +15831,16 @@ function createQueryBuilder(state) {
15734
15831
  }
15735
15832
  return makeExecutableQuery(built, sqlText);
15736
15833
  },
15737
- execute() {
15738
- return runWithHooks(built, "update");
15834
+ async execute() {
15835
+ const result = await runWithHooks(built, "update");
15836
+ return mutationCount(result);
15739
15837
  },
15740
15838
  async executeTakeFirst() {
15741
- const result = await runWithHooks(built, "update");
15839
+ const result = mutationCount(await runWithHooks(built, "update"));
15742
15840
  return { numUpdatedRows: result };
15743
15841
  },
15744
15842
  async executeTakeFirstOrThrow() {
15745
- const result = await runWithHooks(built, "update");
15843
+ const result = mutationCount(await runWithHooks(built, "update"));
15746
15844
  if (result === 0)
15747
15845
  throw new Error("No rows updated");
15748
15846
  return { numUpdatedRows: result };
@@ -15863,18 +15961,18 @@ function createQueryBuilder(state) {
15863
15961
  } catch (err) {
15864
15962
  throw err;
15865
15963
  }
15866
- const result = await runWithHooks(ensureDelBuilt(), "delete");
15964
+ const result = mutationCount(await runWithHooks(ensureDelBuilt(), "delete"));
15867
15965
  try {
15868
15966
  await config5.hooks?.afterDelete?.({ table: String(table), where: whereCondition, result });
15869
15967
  } catch {}
15870
15968
  return result;
15871
15969
  },
15872
15970
  async executeTakeFirst() {
15873
- const result = await runWithHooks(ensureDelBuilt(), "delete");
15971
+ const result = mutationCount(await runWithHooks(ensureDelBuilt(), "delete"));
15874
15972
  return { numDeletedRows: result };
15875
15973
  },
15876
15974
  async executeTakeFirstOrThrow() {
15877
- const result = await runWithHooks(ensureDelBuilt(), "delete");
15975
+ const result = mutationCount(await runWithHooks(ensureDelBuilt(), "delete"));
15878
15976
  if (result === 0)
15879
15977
  throw new Error("No rows deleted");
15880
15978
  return { numDeletedRows: result };
@@ -16589,7 +16687,7 @@ var init_cache = __esm(() => {
16589
16687
  });
16590
16688
 
16591
16689
  // src/actions/console.ts
16592
- import process21 from "process";
16690
+ import process22 from "process";
16593
16691
  import { createInterface } from "readline";
16594
16692
  async function startConsole() {
16595
16693
  console.log("-- Query Builder Interactive Console");
@@ -16598,8 +16696,8 @@ async function startConsole() {
16598
16696
  console.log();
16599
16697
  const qb = createQueryBuilder();
16600
16698
  const rl = createInterface({
16601
- input: process21.stdin,
16602
- output: process21.stdout,
16699
+ input: process22.stdin,
16700
+ output: process22.stdout,
16603
16701
  prompt: "qb> "
16604
16702
  });
16605
16703
  let multilineBuffer = "";
@@ -16650,7 +16748,7 @@ Tips:
16650
16748
  if (cmd === ".exit" || cmd === ".quit") {
16651
16749
  console.log("Goodbye!");
16652
16750
  rl.close();
16653
- process21.exit(0);
16751
+ process22.exit(0);
16654
16752
  } else if (cmd === ".help") {
16655
16753
  console.log(helpText);
16656
16754
  } else if (cmd === ".clear") {
@@ -16721,7 +16819,7 @@ Tips:
16721
16819
  rl.on("close", () => {
16722
16820
  console.log(`
16723
16821
  Goodbye!`);
16724
- process21.exit(0);
16822
+ process22.exit(0);
16725
16823
  });
16726
16824
  rl.prompt();
16727
16825
  }
@@ -17099,9 +17197,8 @@ var init_db_info = __esm(() => {
17099
17197
  });
17100
17198
 
17101
17199
  // src/actions/db-optimize.ts
17102
- import process22 from "process";
17103
17200
  async function dbOptimize(options = {}) {
17104
- const dialect = options.dialect || process22.env.DB_DIALECT || config5.dialect || "postgres";
17201
+ const dialect = resolveDialect(options.dialect);
17105
17202
  const aggressive = options.aggressive || false;
17106
17203
  if (options.verbose) {
17107
17204
  console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
@@ -17141,7 +17238,7 @@ async function dbOptimize(options = {}) {
17141
17238
  await bunSql`ANALYZE TABLE ${bunSql(table)}`;
17142
17239
  }
17143
17240
  } else {
17144
- const dbName = process22.env.DB_NAME || "test";
17241
+ const dbName = resolveDatabaseName();
17145
17242
  const tables = await bunSql`
17146
17243
  SELECT table_name
17147
17244
  FROM information_schema.tables
@@ -17194,9 +17291,8 @@ var init_db_optimize = __esm(() => {
17194
17291
  });
17195
17292
 
17196
17293
  // src/actions/db-wipe.ts
17197
- import process23 from "process";
17198
17294
  async function dbWipe(options = {}) {
17199
- const dialect = options.dialect || process23.env.DB_DIALECT || config5.dialect || "postgres";
17295
+ const dialect = resolveDialect(options.dialect);
17200
17296
  if (options.verbose) {
17201
17297
  console.log(`Wiping all tables from ${dialect} database...`);
17202
17298
  }
@@ -17210,7 +17306,7 @@ async function dbWipe(options = {}) {
17210
17306
  `;
17211
17307
  tables = result.map((row) => row.tablename);
17212
17308
  } else if (isMysqlLike(dialect)) {
17213
- const dbName = process23.env.DB_NAME || "test";
17309
+ const dbName = resolveDatabaseName();
17214
17310
  const result = await bunSql`
17215
17311
  SELECT table_name
17216
17312
  FROM information_schema.tables
@@ -17647,6 +17743,8 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17647
17743
  return "decimal";
17648
17744
  if (base === "date")
17649
17745
  return "date";
17746
+ if (base === "timestamptz" || /\bwith time zone\b/.test(t))
17747
+ return "timestamptz";
17650
17748
  if (/^(?:timestamp|datetime)/.test(base))
17651
17749
  return "datetime";
17652
17750
  if (/^time/.test(base))
@@ -17657,10 +17755,6 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17657
17755
  return "enum";
17658
17756
  return "string";
17659
17757
  }
17660
- function stripIndexPrefix(physicalName, table) {
17661
- const prefix = `${table}_`;
17662
- return physicalName.startsWith(prefix) ? physicalName.slice(prefix.length) : physicalName;
17663
- }
17664
17758
  function asAction(raw2) {
17665
17759
  const v = String(raw2 ?? "").toLowerCase().trim();
17666
17760
  if (v === "cascade" || v === "restrict" || v === "set null")
@@ -17696,7 +17790,7 @@ async function buildSqliteTable(qb, table) {
17696
17790
  const isUnique = Number(idx.unique) === 1;
17697
17791
  if (isUnique && columns2.length === 1)
17698
17792
  uniqueColumns.add(columns2[0]);
17699
- indexes.push({ name: stripIndexPrefix(physName, table), columns: columns2, type: isUnique ? "unique" : "index" });
17793
+ indexes.push({ name: physName, columns: columns2, type: isUnique ? "unique" : "index" });
17700
17794
  }
17701
17795
  const columns = colRows.map((r) => {
17702
17796
  const name = String(r.name);
@@ -17733,7 +17827,31 @@ function normalizeRawDefault(raw2) {
17733
17827
  return raw2;
17734
17828
  return String(raw2);
17735
17829
  }
17736
- async function buildPgTable(qb, table) {
17830
+ async function pgEnumLabels(qb) {
17831
+ const rows = await qb.unsafe(`SELECT t.typname AS type_name, e.enumlabel AS label
17832
+ FROM pg_type t
17833
+ JOIN pg_enum e ON e.enumtypid = t.oid
17834
+ JOIN pg_namespace n ON n.oid = t.typnamespace
17835
+ WHERE n.nspname = 'public'
17836
+ ORDER BY t.typname, e.enumsortorder`);
17837
+ const labels = new Map;
17838
+ for (const row of rows) {
17839
+ const name = String(row.type_name);
17840
+ const existing = labels.get(name);
17841
+ if (existing)
17842
+ existing.push(String(row.label));
17843
+ else
17844
+ labels.set(name, [String(row.label)]);
17845
+ }
17846
+ return labels;
17847
+ }
17848
+ function pgColumnType(dataType, udtName, enums) {
17849
+ const enumValues = enums.get(udtName);
17850
+ const type = sqlTypeToNormalized(String(dataType || udtName || ""), "postgres", { enumValues });
17851
+ return enumValues ? { type, enumValues, enumTypeName: udtName } : { type };
17852
+ }
17853
+ async function buildPgTable(qb, table, enumLabels) {
17854
+ const enums = enumLabels ?? await pgEnumLabels(qb);
17737
17855
  const colRows = await qb.unsafe(`SELECT c.column_name, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length,
17738
17856
  (SELECT COUNT(*) FROM information_schema.table_constraints tc
17739
17857
  JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
@@ -17781,7 +17899,7 @@ async function buildPgTable(qb, table) {
17781
17899
  const hasDefault = rawDefault != null && !isSerial;
17782
17900
  return {
17783
17901
  name,
17784
- type: sqlTypeToNormalized(String(r.data_type ?? r.udt_name ?? ""), "postgres"),
17902
+ ...pgColumnType(String(r.data_type ?? ""), r.udt_name == null ? "" : String(r.udt_name), enums),
17785
17903
  isPrimaryKey,
17786
17904
  isUnique: uniqueColumns.has(name),
17787
17905
  isNullable: isPrimaryKey ? false : String(r.is_nullable).toUpperCase() === "YES",
@@ -17867,7 +17985,7 @@ function groupPhysicalIndexes(table, rows) {
17867
17985
  if (entry.isUnique && entry.columns.length === 1)
17868
17986
  uniqueColumns.add(entry.columns[0]);
17869
17987
  indexes.push({
17870
- name: stripIndexPrefix(physName, table),
17988
+ name: physName,
17871
17989
  columns: entry.columns,
17872
17990
  type: entry.isUnique ? "unique" : "index",
17873
17991
  where: entry.where
@@ -17880,13 +17998,14 @@ async function buildPlanFromDatabase(dialect, opts = {}) {
17880
17998
  const qb = createQueryBuilder();
17881
17999
  const allTables = opts.tables?.length ? opts.tables : await listTables(qb, d);
17882
18000
  const tables = allTables.filter((t) => t !== "migrations");
18001
+ const enumLabels = d === "postgres" ? await pgEnumLabels(qb) : undefined;
17883
18002
  const out = [];
17884
18003
  for (const table of tables) {
17885
18004
  let plan;
17886
18005
  if (d === "sqlite")
17887
18006
  plan = await buildSqliteTable(qb, table);
17888
18007
  else if (d === "postgres")
17889
- plan = await buildPgTable(qb, table);
18008
+ plan = await buildPgTable(qb, table, enumLabels);
17890
18009
  else
17891
18010
  plan = await buildMysqlTable(qb, table);
17892
18011
  if (plan.columns.length > 0)
@@ -17902,7 +18021,7 @@ var init_introspect_db = __esm(() => {
17902
18021
  // src/actions/make-model.ts
17903
18022
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
17904
18023
  import { dirname as dirname7, join as join7 } from "path";
17905
- import process24 from "process";
18024
+ import process23 from "process";
17906
18025
  function findWorkspaceRoot(startPath) {
17907
18026
  let currentPath = startPath;
17908
18027
  while (currentPath !== dirname7(currentPath)) {
@@ -17911,10 +18030,10 @@ function findWorkspaceRoot(startPath) {
17911
18030
  }
17912
18031
  currentPath = dirname7(currentPath);
17913
18032
  }
17914
- return process24.cwd();
18033
+ return process23.cwd();
17915
18034
  }
17916
18035
  async function makeModel(name, options = {}) {
17917
- const workspaceRoot = findWorkspaceRoot(process24.cwd());
18036
+ const workspaceRoot = findWorkspaceRoot(process23.cwd());
17918
18037
  const modelsDir = options.dir || join7(workspaceRoot, "app/Models");
17919
18038
  if (!existsSync14(modelsDir)) {
17920
18039
  mkdirSync7(modelsDir, { recursive: true });
@@ -17957,6 +18076,12 @@ export default model
17957
18076
  }
17958
18077
  var init_make_model = () => {};
17959
18078
 
18079
+ // src/drivers/index-name.ts
18080
+ function qualifiedIndexName(tableName, indexName) {
18081
+ const prefix = `${tableName}_`;
18082
+ return indexName.startsWith(prefix) ? indexName : `${prefix}${indexName}`;
18083
+ }
18084
+
17960
18085
  // src/drivers/mysql.ts
17961
18086
  class MySQLDriver {
17962
18087
  quoteIdentifier(id) {
@@ -18043,7 +18168,7 @@ class MySQLDriver {
18043
18168
  throw new Error(`[migrations] Partial indexes (CompositeIndex.where) are not supported on MySQL. Index '${index.name}' on table '${tableName}' uses WHERE clause: ${index.where}`);
18044
18169
  }
18045
18170
  const kind = index.type === "unique" ? "UNIQUE " : "";
18046
- const idxName = `${tableName}_${index.name}`;
18171
+ const idxName = qualifiedIndexName(tableName, index.name);
18047
18172
  const columns = index.columns.map((c) => this.quoteIdentifier(c)).join(", ");
18048
18173
  return `CREATE ${kind}INDEX ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns});`;
18049
18174
  }
@@ -18096,7 +18221,7 @@ class MySQLDriver {
18096
18221
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP COLUMN ${this.quoteIdentifier(columnName)};`;
18097
18222
  }
18098
18223
  dropIndex(tableName, indexName) {
18099
- const fullIndexName = `${tableName}_${indexName}`;
18224
+ const fullIndexName = qualifiedIndexName(tableName, indexName);
18100
18225
  return `DROP INDEX ${this.quoteIdentifier(fullIndexName)} ON ${this.quoteIdentifier(tableName)};`;
18101
18226
  }
18102
18227
  dropEnumType(_enumTypeName) {
@@ -18110,7 +18235,7 @@ class MySQLDriver {
18110
18235
  )`;
18111
18236
  }
18112
18237
  getExecutedMigrationsQuery() {
18113
- return "SELECT migration FROM migrations ORDER BY executed_at";
18238
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18114
18239
  }
18115
18240
  recordMigrationQuery() {
18116
18241
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18143,6 +18268,7 @@ class MySQLDriver {
18143
18268
  return parts.join(" ");
18144
18269
  }
18145
18270
  }
18271
+ var init_mysql = () => {};
18146
18272
 
18147
18273
  // src/drivers/postgres.ts
18148
18274
  class PostgresDriver {
@@ -18177,7 +18303,7 @@ class PostgresDriver {
18177
18303
  return "jsonb";
18178
18304
  case "enum":
18179
18305
  if (column.enumValues && column.enumValues.length > 0) {
18180
- return this.quoteIdentifier(column.enumTypeName ?? `${column.name}_type`);
18306
+ return column.enumTypeName ? this.quoteIdentifier(column.enumTypeName) : "text";
18181
18307
  }
18182
18308
  return "text";
18183
18309
  default:
@@ -18231,7 +18357,7 @@ class PostgresDriver {
18231
18357
  }
18232
18358
  createIndex(tableName, index) {
18233
18359
  const kind = index.type === "unique" ? "UNIQUE " : "";
18234
- const idxName = `${tableName}_${index.name}`;
18360
+ const idxName = qualifiedIndexName(tableName, index.name);
18235
18361
  const columns = index.columns.map((c) => this.quoteIdentifier(c)).join(", ");
18236
18362
  const where = index.where ? ` WHERE ${index.where}` : "";
18237
18363
  return `CREATE ${kind}INDEX IF NOT EXISTS ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns})${where};`;
@@ -18259,7 +18385,17 @@ class PostgresDriver {
18259
18385
  }
18260
18386
  modifyColumn(tableName, column) {
18261
18387
  const typeSql = this.getColumnType(column);
18262
- return `ALTER TABLE ${this.quoteIdentifier(tableName)} ALTER COLUMN ${this.quoteIdentifier(column.name)} TYPE ${typeSql} USING ${this.quoteIdentifier(column.name)}::${typeSql};`;
18388
+ const table = this.quoteIdentifier(tableName);
18389
+ const name = this.quoteIdentifier(column.name);
18390
+ const statements = [
18391
+ `ALTER TABLE ${table} ALTER COLUMN ${name} TYPE ${typeSql} USING ${name}::${typeSql};`
18392
+ ];
18393
+ if (!column.isPrimaryKey)
18394
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${name} ${column.isNullable ? "DROP NOT NULL" : "SET NOT NULL"};`);
18395
+ const defaultValue = this.getDefaultValue(column);
18396
+ statements.push(defaultValue ? `ALTER TABLE ${table} ALTER COLUMN ${name} SET ${defaultValue};` : `ALTER TABLE ${table} ALTER COLUMN ${name} DROP DEFAULT;`);
18397
+ return statements.join(`
18398
+ `);
18263
18399
  }
18264
18400
  renameColumn(tableName, from, to) {
18265
18401
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
@@ -18277,7 +18413,7 @@ class PostgresDriver {
18277
18413
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP COLUMN ${this.quoteIdentifier(columnName)};`;
18278
18414
  }
18279
18415
  dropIndex(tableName, indexName) {
18280
- const fullIndexName = `${tableName}_${indexName}`;
18416
+ const fullIndexName = qualifiedIndexName(tableName, indexName);
18281
18417
  return `DROP INDEX IF EXISTS ${this.quoteIdentifier(fullIndexName)};`;
18282
18418
  }
18283
18419
  dropEnumType(enumTypeName) {
@@ -18291,7 +18427,7 @@ class PostgresDriver {
18291
18427
  )`;
18292
18428
  }
18293
18429
  getExecutedMigrationsQuery() {
18294
- return "SELECT migration FROM migrations ORDER BY executed_at";
18430
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18295
18431
  }
18296
18432
  recordMigrationQuery() {
18297
18433
  return "INSERT INTO migrations (migration) VALUES ($1)";
@@ -18320,10 +18456,12 @@ class PostgresDriver {
18320
18456
  return parts.join(" ");
18321
18457
  }
18322
18458
  }
18459
+ var init_postgres = () => {};
18323
18460
 
18324
18461
  // src/drivers/singlestore.ts
18325
18462
  var SingleStoreDriver;
18326
18463
  var init_singlestore = __esm(() => {
18464
+ init_mysql();
18327
18465
  SingleStoreDriver = class SingleStoreDriver extends MySQLDriver {
18328
18466
  createTable(table) {
18329
18467
  const columns = table.columns.map((c) => this.renderColumn(c)).join(`,
@@ -18457,7 +18595,7 @@ class SQLiteDriver {
18457
18595
  }
18458
18596
  createIndex(tableName, index) {
18459
18597
  const kind = index.type === "unique" ? "UNIQUE " : "";
18460
- const idxName = `${tableName}_${index.name}`;
18598
+ const idxName = qualifiedIndexName(tableName, index.name);
18461
18599
  const columns = index.columns.map((c) => this.quoteIdentifier(c)).join(", ");
18462
18600
  const where = index.where ? ` WHERE ${index.where}` : "";
18463
18601
  return `CREATE ${kind}INDEX IF NOT EXISTS ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns})${where};`;
@@ -18522,7 +18660,7 @@ class SQLiteDriver {
18522
18660
  return `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP COLUMN ${this.quoteIdentifier(columnName)};`;
18523
18661
  }
18524
18662
  dropIndex(tableName, indexName) {
18525
- const fullIndexName = `${tableName}_${indexName}`;
18663
+ const fullIndexName = qualifiedIndexName(tableName, indexName);
18526
18664
  return `DROP INDEX IF EXISTS ${this.quoteIdentifier(fullIndexName)};`;
18527
18665
  }
18528
18666
  dropEnumType(_enumTypeName) {
@@ -18536,7 +18674,7 @@ class SQLiteDriver {
18536
18674
  )`;
18537
18675
  }
18538
18676
  getExecutedMigrationsQuery() {
18539
- return "SELECT migration FROM migrations ORDER BY executed_at";
18677
+ return "SELECT migration, executed_at FROM migrations ORDER BY executed_at";
18540
18678
  }
18541
18679
  recordMigrationQuery() {
18542
18680
  return "INSERT INTO migrations (migration) VALUES (?)";
@@ -18568,6 +18706,7 @@ class SQLiteDriver {
18568
18706
  return parts.join(" ");
18569
18707
  }
18570
18708
  }
18709
+ var init_sqlite = () => {};
18571
18710
 
18572
18711
  // src/drivers/index.ts
18573
18712
  function getDialectDriver(dialect) {
@@ -18585,10 +18724,34 @@ function getDialectDriver(dialect) {
18585
18724
  }
18586
18725
  }
18587
18726
  var init_drivers = __esm(() => {
18727
+ init_mysql();
18728
+ init_postgres();
18588
18729
  init_singlestore();
18730
+ init_sqlite();
18731
+ init_mysql();
18732
+ init_postgres();
18589
18733
  init_singlestore();
18734
+ init_sqlite();
18590
18735
  });
18591
18736
 
18737
+ // src/workspace.ts
18738
+ import { existsSync as existsSync16 } from "fs";
18739
+ import { dirname as dirname8, join as join9 } from "path";
18740
+ import process24 from "process";
18741
+ function findWorkspaceRoot2(startPath = process24.cwd()) {
18742
+ let currentPath = startPath;
18743
+ while (currentPath !== dirname8(currentPath)) {
18744
+ if (existsSync16(join9(currentPath, "package.json")))
18745
+ return currentPath;
18746
+ currentPath = dirname8(currentPath);
18747
+ }
18748
+ return process24.cwd();
18749
+ }
18750
+ function getSqlDirectory(workspaceRoot) {
18751
+ return join9(workspaceRoot ?? findWorkspaceRoot2(), "database", "migrations");
18752
+ }
18753
+ var init_workspace = () => {};
18754
+
18592
18755
  // src/actions/migrate.ts
18593
18756
  var exports_migrate = {};
18594
18757
  __export(exports_migrate, {
@@ -18600,23 +18763,48 @@ __export(exports_migrate, {
18600
18763
  copyModelsToGenerated: () => copyModelsToGenerated,
18601
18764
  clearGeneratedDirectory: () => clearGeneratedDirectory
18602
18765
  });
18603
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18766
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
18604
18767
  import { tmpdir } from "os";
18605
- import { join as join9 } from "path";
18768
+ import { join as join10 } from "path";
18606
18769
  import process25 from "process";
18770
+ async function settleAppliedFiles(qb, dialect, files) {
18771
+ if (files.length === 0)
18772
+ return;
18773
+ const sqlDir = ensureSqlDirectory(getWorkspaceRoot());
18774
+ try {
18775
+ await createMigrationsTable(qb, dialect);
18776
+ for (const file2 of files) {
18777
+ await recordMigration(qb, file2, dialect);
18778
+ info(`-- \u2713 Recorded applied migration: ${file2}`);
18779
+ }
18780
+ } catch (err) {
18781
+ info(`-- Could not record applied migrations (the schema change itself succeeded): ${err instanceof Error ? err.message : String(err)}`);
18782
+ }
18783
+ }
18784
+ function isGeneratedMigration(sqlDir, file2) {
18785
+ try {
18786
+ return isGeneratedMigrationSql(readFileSync3(join10(sqlDir, file2), "utf8"));
18787
+ } catch {
18788
+ return false;
18789
+ }
18790
+ }
18791
+ function isAlreadyAppliedError(err) {
18792
+ const message = err instanceof Error ? err.message : String(err);
18793
+ return /duplicate column name/i.test(message) || /duplicate key name/i.test(message) || /\balready exists\b/i.test(message);
18794
+ }
18607
18795
  function info(message) {
18608
18796
  if (config5.verbose)
18609
18797
  console.log(message);
18610
18798
  }
18611
18799
  function getSnapshotDir(workspaceRoot) {
18612
- return join9(workspaceRoot, config5.snapshotDir || ".qb");
18800
+ return join10(workspaceRoot, config5.snapshotDir || ".qb");
18613
18801
  }
18614
18802
  function getSnapshotPath(workspaceRoot, dialect) {
18615
- return join9(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18803
+ return join10(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18616
18804
  }
18617
18805
  function loadPlanSnapshot(workspaceRoot, dialect) {
18618
18806
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18619
- if (!existsSync16(snapshotPath)) {
18807
+ if (!existsSync17(snapshotPath)) {
18620
18808
  return;
18621
18809
  }
18622
18810
  try {
@@ -18639,7 +18827,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18639
18827
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18640
18828
  const snapshotDir = getSnapshotDir(workspaceRoot);
18641
18829
  const hash = hashMigrationPlan(plan);
18642
- if (existsSync16(snapshotPath)) {
18830
+ if (existsSync17(snapshotPath)) {
18643
18831
  try {
18644
18832
  const current = JSON.parse(readFileSync3(snapshotPath, "utf8"));
18645
18833
  if (current?.hash === hash && current?.dialect === dialect && current?.plan) {
@@ -18648,7 +18836,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
18648
18836
  }
18649
18837
  } catch {}
18650
18838
  }
18651
- if (!existsSync16(snapshotDir)) {
18839
+ if (!existsSync17(snapshotDir)) {
18652
18840
  mkdirSync8(snapshotDir, { recursive: true });
18653
18841
  info(`-- Created snapshot directory: ${snapshotDir}`);
18654
18842
  }
@@ -18666,11 +18854,11 @@ function saveMigrationSnapshot(plan, options = {}) {
18666
18854
  savePlanSnapshot(options.workspaceRoot ?? getWorkspaceRoot(), dialect, plan);
18667
18855
  }
18668
18856
  function getWorkspaceRoot() {
18669
- return process25.cwd();
18857
+ return findWorkspaceRoot2();
18670
18858
  }
18671
18859
  function ensureSqlDirectory(workspaceRoot) {
18672
18860
  const sqlDir = getSqlDirectory(workspaceRoot);
18673
- if (!existsSync16(sqlDir)) {
18861
+ if (!existsSync17(sqlDir)) {
18674
18862
  mkdirSync8(sqlDir, { recursive: true });
18675
18863
  info(`-- Created SQL directory: ${sqlDir}`);
18676
18864
  }
@@ -18679,21 +18867,22 @@ function ensureSqlDirectory(workspaceRoot) {
18679
18867
  async function generateMigration(dir, opts = {}) {
18680
18868
  await getConfig();
18681
18869
  if (!dir) {
18682
- dir = join9(process25.cwd(), "app/Models");
18870
+ dir = join10(process25.cwd(), "app/Models");
18683
18871
  }
18684
18872
  const dialect = opts.dialect || config5.dialect || "postgres";
18685
18873
  const workspaceRoot = getWorkspaceRoot();
18686
18874
  const models = await loadModels({ modelsDir: dir });
18687
18875
  const plan = buildMigrationPlan2(models, { dialect });
18688
18876
  let previous;
18877
+ let reconciledFromDatabase = false;
18689
18878
  if (!opts.full) {
18690
18879
  previous = loadPlanSnapshot(workspaceRoot, dialect);
18691
18880
  if (previous) {
18692
18881
  info("-- Comparing with stored model snapshot");
18693
18882
  } else {
18694
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
18883
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18695
18884
  const statePath = String(opts.state || defaultStatePath);
18696
- if (existsSync16(statePath)) {
18885
+ if (existsSync17(statePath)) {
18697
18886
  try {
18698
18887
  const raw2 = readFileSync3(statePath, "utf8");
18699
18888
  const parsed = JSON.parse(raw2);
@@ -18710,6 +18899,7 @@ async function generateMigration(dir, opts = {}) {
18710
18899
  const scoped = livePlan.tables.filter((t) => modelTables.has(t.table));
18711
18900
  if (scoped.length > 0) {
18712
18901
  previous = { dialect: livePlan.dialect, tables: scoped };
18902
+ reconciledFromDatabase = true;
18713
18903
  info(`-- No snapshot; reconciling against live database (${scoped.length} matching table${scoped.length === 1 ? "" : "s"})`);
18714
18904
  }
18715
18905
  } catch (err) {
@@ -18723,25 +18913,34 @@ async function generateMigration(dir, opts = {}) {
18723
18913
  } else {
18724
18914
  info("-- Full migration requested, ignoring any previous state");
18725
18915
  }
18726
- 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 });
18916
+ 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, {
18917
+ applyRenames: opts.applyRenames,
18918
+ dryRun: opts.dryRun,
18919
+ preserveUnknownColumns: reconciledFromDatabase
18920
+ });
18727
18921
  const sqlStatements = diff.statements;
18728
18922
  const operations = diff.operations;
18729
18923
  const sql = sqlStatements.join(`
18730
18924
  `);
18731
18925
  const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER|DROP)\b/i.test(stmt));
18732
18926
  if (opts.apply) {
18733
- const dirPath = mkdtempSync(join9(tmpdir(), "qb-migrate-"));
18734
- const filePath = join9(dirPath, "migration.sql");
18927
+ const dirPath = mkdtempSync(join10(tmpdir(), "qb-migrate-"));
18928
+ const filePath = join10(dirPath, "migration.sql");
18735
18929
  try {
18736
18930
  if (hasChanges) {
18737
18931
  writeFileSync11(filePath, sql);
18932
+ const qb = createQueryBuilder();
18933
+ await qb.file(filePath);
18738
18934
  info("-- Migration applied");
18935
+ await settleAppliedFiles(qb, dialect, lastCreatedMigrationFiles());
18739
18936
  } else {
18740
18937
  info("-- No changes; nothing to apply");
18741
18938
  }
18742
18939
  } catch (err) {
18743
18940
  console.error("-- Migration failed:", err);
18744
18941
  throw err;
18942
+ } finally {
18943
+ rmSync(dirPath, { recursive: true, force: true });
18745
18944
  }
18746
18945
  }
18747
18946
  if (!opts.dryRun)
@@ -18750,9 +18949,6 @@ async function generateMigration(dir, opts = {}) {
18750
18949
  }
18751
18950
  async function executeMigration(dir) {
18752
18951
  await getConfig();
18753
- if (!dir) {
18754
- dir = join9(process25.cwd(), "app/Models");
18755
- }
18756
18952
  const workspaceRoot = getWorkspaceRoot();
18757
18953
  const sqlDir = ensureSqlDirectory(workspaceRoot);
18758
18954
  const dialect = config5.dialect || "postgres";
@@ -18767,42 +18963,25 @@ async function executeMigration(dir) {
18767
18963
  const qb = createQueryBuilder();
18768
18964
  await createMigrationsTable(qb, dialect);
18769
18965
  const executedMigrations = await getExecutedMigrations(qb, dialect);
18770
- const permanentMigrations = [];
18771
- const transientMigrations = [];
18772
- for (const file2 of scriptFiles) {
18773
- if (file2.includes("alter-") && file2.includes("-table")) {
18774
- transientMigrations.push(file2);
18775
- } else if (!executedMigrations.includes(file2)) {
18776
- permanentMigrations.push(file2);
18777
- }
18778
- }
18779
- const totalPending = permanentMigrations.length + transientMigrations.length;
18780
- if (totalPending === 0) {
18966
+ const pending = scriptFiles.filter((file2) => !executedMigrations.includes(file2));
18967
+ if (pending.length === 0) {
18781
18968
  info("-- No pending migrations to execute");
18782
18969
  return true;
18783
18970
  }
18784
- info(`-- Executing ${totalPending} migrations (${permanentMigrations.length} permanent, ${transientMigrations.length} transient)`);
18785
- for (const file2 of permanentMigrations) {
18786
- const filePath = join9(sqlDir, file2);
18971
+ info(`-- Executing ${pending.length} migration${pending.length === 1 ? "" : "s"}`);
18972
+ for (const file2 of pending) {
18973
+ const filePath = join10(sqlDir, file2);
18787
18974
  info(`-- Executing: ${file2}`);
18788
18975
  try {
18789
18976
  await qb.file(filePath);
18790
18977
  await recordMigration(qb, file2, dialect);
18791
18978
  info(`-- \u2713 Migration ${file2} executed and recorded`);
18792
18979
  } catch (err) {
18793
- console.error(`-- \u2717 Migration ${file2} failed:`, err);
18794
- throw err;
18795
- }
18796
- }
18797
- for (const file2 of transientMigrations) {
18798
- const filePath = join9(sqlDir, file2);
18799
- info(`-- Executing: ${file2} (transient)`);
18800
- try {
18801
- await qb.file(filePath);
18802
- info(`-- \u2713 Migration ${file2} executed (not recorded)`);
18803
- unlinkSync(filePath);
18804
- info(`-- \uD83D\uDDD1\uFE0F Deleted transient migration: ${file2}`);
18805
- } catch (err) {
18980
+ if (isGeneratedMigration(sqlDir, file2) && isAlreadyAppliedError(err)) {
18981
+ await recordMigration(qb, file2, dialect);
18982
+ info(`-- \u2713 Migration ${file2} was already applied; recorded it (${err instanceof Error ? err.message : String(err)})`);
18983
+ continue;
18984
+ }
18806
18985
  console.error(`-- \u2717 Migration ${file2} failed:`, err);
18807
18986
  throw err;
18808
18987
  }
@@ -18817,7 +18996,7 @@ async function executeMigration(dir) {
18817
18996
  async function resetDatabase(dir, opts = {}) {
18818
18997
  await getConfig();
18819
18998
  if (!dir) {
18820
- dir = join9(process25.cwd(), "app/Models");
18999
+ dir = join10(process25.cwd(), "app/Models");
18821
19000
  }
18822
19001
  const dialect = opts.dialect || config5.dialect || "postgres";
18823
19002
  const driver = getDialectDriver(dialect);
@@ -18914,42 +19093,50 @@ async function resetDatabase(dir, opts = {}) {
18914
19093
  }
18915
19094
  async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
18916
19095
  if (!dir) {
18917
- dir = join9(process25.cwd(), "app/Models");
19096
+ dir = join10(process25.cwd(), "app/Models");
18918
19097
  }
18919
19098
  if (!workspaceRoot) {
18920
19099
  workspaceRoot = getWorkspaceRoot();
18921
19100
  }
18922
19101
  const dialect = String(opts.dialect || config5.dialect || "postgres");
18923
19102
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18924
- if (existsSync16(snapshotPath)) {
19103
+ if (existsSync17(snapshotPath)) {
18925
19104
  unlinkSync(snapshotPath);
18926
19105
  info(`-- Removed model snapshot file: ${snapshotPath}`);
18927
19106
  }
18928
- const defaultStatePath = join9(dir, `.qb-migrations.${dialect}.json`);
19107
+ const defaultStatePath = join10(dir, `.qb-migrations.${dialect}.json`);
18929
19108
  const statePath = String(opts.state || defaultStatePath);
18930
- if (existsSync16(statePath)) {
19109
+ if (existsSync17(statePath)) {
18931
19110
  unlinkSync(statePath);
18932
19111
  info(`-- Removed legacy migration state file: ${statePath}`);
18933
19112
  }
18934
- const sqlDir = getSqlDirectory(workspaceRoot);
18935
- if (existsSync16(sqlDir)) {
18936
- const sqlFiles = readdirSync6(sqlDir);
18937
- const migrationFiles = sqlFiles.filter((file2) => file2.endsWith(".sql"));
18938
- for (const file2 of migrationFiles) {
18939
- const filePath = join9(sqlDir, file2);
18940
- unlinkSync(filePath);
18941
- info(`-- Removed migration file: ${file2}`);
19113
+ removeGeneratedMigrationFiles(getSqlDirectory(workspaceRoot));
19114
+ }
19115
+ function removeGeneratedMigrationFiles(sqlDir) {
19116
+ if (!existsSync17(sqlDir))
19117
+ return;
19118
+ let removed = 0;
19119
+ const kept = [];
19120
+ for (const file2 of readdirSync6(sqlDir).filter((f) => f.endsWith(".sql"))) {
19121
+ if (!isGeneratedMigration(sqlDir, file2)) {
19122
+ kept.push(file2);
19123
+ continue;
18942
19124
  }
18943
- info(`-- Cleaned up ${migrationFiles.length} migration files from migrations directory`);
19125
+ unlinkSync(join10(sqlDir, file2));
19126
+ info(`-- Removed generated migration file: ${file2}`);
19127
+ removed += 1;
18944
19128
  }
19129
+ info(`-- Cleaned up ${removed} generated migration file${removed === 1 ? "" : "s"} from migrations directory`);
19130
+ if (kept.length > 0)
19131
+ info(`-- Kept ${kept.length} hand-written migration${kept.length === 1 ? "" : "s"}: ${kept.slice(0, 5).join(", ")}${kept.length > 5 ? `, +${kept.length - 5} more` : ""}`);
18945
19132
  }
18946
19133
  async function copyModelsToGenerated(_dir, _workspaceRoot) {}
18947
19134
  async function clearGeneratedDirectory(workspaceRoot) {
18948
19135
  if (!workspaceRoot) {
18949
19136
  workspaceRoot = getWorkspaceRoot();
18950
19137
  }
18951
- const generatedDir = join9(workspaceRoot, "generated");
18952
- if (existsSync16(generatedDir)) {
19138
+ const generatedDir = join10(workspaceRoot, "generated");
19139
+ if (existsSync17(generatedDir)) {
18953
19140
  try {
18954
19141
  rmSync(generatedDir, { recursive: true, force: true });
18955
19142
  info("-- Cleared generated directory");
@@ -18958,12 +19145,6 @@ async function clearGeneratedDirectory(workspaceRoot) {
18958
19145
  }
18959
19146
  }
18960
19147
  }
18961
- function getSqlDirectory(workspaceRoot) {
18962
- if (!workspaceRoot) {
18963
- workspaceRoot = getWorkspaceRoot();
18964
- }
18965
- return join9(workspaceRoot, "database", "migrations");
18966
- }
18967
19148
  async function createMigrationsTable(qb, dialect) {
18968
19149
  const driver = getDialectDriver(dialect);
18969
19150
  const createTableSql = driver.createMigrationsTable();
@@ -19002,6 +19183,7 @@ var init_migrate = __esm(() => {
19002
19183
  init_drivers();
19003
19184
  init_src2();
19004
19185
  init_introspect_db();
19186
+ init_workspace();
19005
19187
  });
19006
19188
 
19007
19189
  // src/actions/migrate-generate.ts
@@ -19013,28 +19195,49 @@ var init_migrate_generate = __esm(() => {
19013
19195
  });
19014
19196
 
19015
19197
  // src/actions/migrate-rollback.ts
19016
- import { existsSync as existsSync17, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19017
- import { dirname as dirname8, join as join10 } from "path";
19018
- import process26 from "process";
19198
+ import { existsSync as existsSync18, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
19199
+ import { join as join11 } from "path";
19019
19200
  function splitSqlStatements2(sql) {
19020
19201
  const out = [];
19021
19202
  let buf = "";
19022
- let inString = false;
19023
- const lines = sql.split(`
19024
- `).filter((l) => !/^\s*--/.test(l));
19025
- const text = lines.join(`
19026
- `);
19027
- for (let i = 0;i < text.length; i++) {
19028
- const ch = text[i];
19029
- if (ch === "'")
19030
- inString = !inString;
19031
- if (ch === ";" && !inString) {
19203
+ let quote = null;
19204
+ for (let i = 0;i < sql.length; i++) {
19205
+ const ch = sql[i];
19206
+ if (quote) {
19207
+ buf += ch;
19208
+ if (ch === quote) {
19209
+ if (sql[i + 1] === quote) {
19210
+ buf += sql[i + 1];
19211
+ i++;
19212
+ } else {
19213
+ quote = null;
19214
+ }
19215
+ }
19216
+ continue;
19217
+ }
19218
+ if (ch === "'" || ch === '"' || ch === "`") {
19219
+ quote = ch;
19220
+ buf += ch;
19221
+ continue;
19222
+ }
19223
+ if (ch === "-" && sql[i + 1] === "-") {
19224
+ const newline = sql.indexOf(`
19225
+ `, i);
19226
+ i = newline === -1 ? sql.length : newline;
19227
+ continue;
19228
+ }
19229
+ if (ch === "/" && sql[i + 1] === "*") {
19230
+ const close = sql.indexOf("*/", i + 2);
19231
+ i = close === -1 ? sql.length : close + 1;
19232
+ continue;
19233
+ }
19234
+ if (ch === ";") {
19032
19235
  if (buf.trim())
19033
19236
  out.push(buf.trim());
19034
19237
  buf = "";
19035
- } else {
19036
- buf += ch;
19238
+ continue;
19037
19239
  }
19240
+ buf += ch;
19038
19241
  }
19039
19242
  if (buf.trim())
19040
19243
  out.push(buf.trim());
@@ -19048,8 +19251,20 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19048
19251
  let m;
19049
19252
  if (m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19050
19253
  down.push(`DROP TABLE IF EXISTS ${q(m[1])}`);
19051
- } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
19052
- down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(m[2])}`);
19254
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+CONSTRAINT\s+["`']?(\w+)["`']?([\s\S]*)$/i.exec(stmt)) {
19255
+ if (dialect === "sqlite") {
19256
+ skipped.push(stmt);
19257
+ } else if (isMysqlLike(dialect) && /\bFOREIGN\s+KEY\b/i.test(m[3] ?? "")) {
19258
+ down.push(`ALTER TABLE ${q(m[1])} DROP FOREIGN KEY ${q(m[2])}`);
19259
+ } else {
19260
+ down.push(`ALTER TABLE ${q(m[1])} DROP CONSTRAINT ${q(m[2])}`);
19261
+ }
19262
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+["`']?(\w+)["`']?|["`'](\w+)["`']|(\w+))/i.exec(stmt)) {
19263
+ const column = m[2] ?? m[3] ?? m[4];
19264
+ if (m[4] && /^(?:PRIMARY|UNIQUE|INDEX|KEY|FOREIGN|CHECK|FULLTEXT|SPATIAL)$/i.test(m[4]))
19265
+ skipped.push(stmt);
19266
+ else
19267
+ down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(column)}`);
19053
19268
  } else if (m = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?(?:\s+ON\s+["`']?(\w+)["`']?)?/i.exec(stmt)) {
19054
19269
  down.push(isMysqlLike(dialect) && m[2] ? `DROP INDEX ${q(m[1])} ON ${q(m[2])}` : `DROP INDEX IF EXISTS ${q(m[1])}`);
19055
19270
  } else {
@@ -19058,22 +19273,6 @@ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
19058
19273
  }
19059
19274
  return { down: down.reverse(), skipped };
19060
19275
  }
19061
- function findWorkspaceRoot2(startPath) {
19062
- let currentPath = startPath;
19063
- while (currentPath !== dirname8(currentPath)) {
19064
- if (existsSync17(join10(currentPath, "package.json"))) {
19065
- return currentPath;
19066
- }
19067
- currentPath = dirname8(currentPath);
19068
- }
19069
- return process26.cwd();
19070
- }
19071
- function getSqlDirectory2(workspaceRoot) {
19072
- if (!workspaceRoot) {
19073
- workspaceRoot = findWorkspaceRoot2(process26.cwd());
19074
- }
19075
- return join10(workspaceRoot, "database", "migrations");
19076
- }
19077
19276
  async function migrateRollback(options = {}) {
19078
19277
  const steps = options.steps || 1;
19079
19278
  const reverseSchema = options.reverseSchema !== false;
@@ -19106,12 +19305,12 @@ async function migrateRollback(options = {}) {
19106
19305
  console.log(` - ${migration.migration}`);
19107
19306
  }
19108
19307
  console.log();
19109
- const sqlDir = getSqlDirectory2();
19308
+ const sqlDir = getSqlDirectory();
19110
19309
  let reversedAny = false;
19111
19310
  for (const migration of migrationsToRollback) {
19112
19311
  try {
19113
- const filePath = join10(sqlDir, migration.migration);
19114
- if (reverseSchema && existsSync17(filePath)) {
19312
+ const filePath = join11(sqlDir, migration.migration);
19313
+ if (reverseSchema && existsSync18(filePath)) {
19115
19314
  const forwardSql = readFileSync4(filePath, "utf8");
19116
19315
  const { down, skipped } = deriveDownStatements(forwardSql);
19117
19316
  for (const stmt of down) {
@@ -19122,10 +19321,10 @@ async function migrateRollback(options = {}) {
19122
19321
  if (skipped.length > 0)
19123
19322
  console.log(`-- \u26A0\uFE0F ${skipped.length} statement(s) in ${migration.migration} could not be auto-reversed (data/complex DDL) \u2014 reverse manually.`);
19124
19323
  }
19125
- const deleteSql = `DELETE FROM migrations WHERE migration = $1`;
19324
+ const deleteSql = `DELETE FROM migrations WHERE migration = ${getPlaceholder(1)}`;
19126
19325
  await qb.unsafe(deleteSql, [migration.migration]);
19127
19326
  console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
19128
- if (existsSync17(filePath)) {
19327
+ if (options.deleteFiles && existsSync18(filePath)) {
19129
19328
  unlinkSync2(filePath);
19130
19329
  console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
19131
19330
  }
@@ -19148,37 +19347,20 @@ async function migrateRollback(options = {}) {
19148
19347
  var init_migrate_rollback = __esm(() => {
19149
19348
  init_config();
19150
19349
  init_src2();
19350
+ init_workspace();
19151
19351
  });
19152
19352
 
19153
19353
  // src/actions/migrate-status.ts
19154
- import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
19155
- import { dirname as dirname9, join as join11 } from "path";
19156
- import process27 from "process";
19157
- function findWorkspaceRoot3(startPath) {
19158
- let currentPath = startPath;
19159
- while (currentPath !== dirname9(currentPath)) {
19160
- if (existsSync18(join11(currentPath, "package.json"))) {
19161
- return currentPath;
19162
- }
19163
- currentPath = dirname9(currentPath);
19164
- }
19165
- return process27.cwd();
19166
- }
19167
- function getSqlDirectory3(workspaceRoot) {
19168
- if (!workspaceRoot) {
19169
- workspaceRoot = findWorkspaceRoot3(process27.cwd());
19170
- }
19171
- return join11(workspaceRoot, "database", "migrations");
19172
- }
19354
+ import { existsSync as existsSync19, readdirSync as readdirSync8 } from "fs";
19173
19355
  async function migrateStatus() {
19174
19356
  const dialect = config5.dialect || "postgres";
19175
19357
  const driver = getDialectDriver(dialect);
19176
- const sqlDir = getSqlDirectory3();
19358
+ const sqlDir = getSqlDirectory();
19177
19359
  console.log("-- Migration Status");
19178
19360
  console.log(`-- Dialect: ${dialect}`);
19179
19361
  console.log(`-- SQL directory: ${sqlDir}`);
19180
19362
  console.log();
19181
- if (!existsSync18(sqlDir)) {
19363
+ if (!existsSync19(sqlDir)) {
19182
19364
  console.log("-- No SQL directory found. No migrations have been created yet.");
19183
19365
  return [];
19184
19366
  }
@@ -19199,27 +19381,16 @@ async function migrateStatus() {
19199
19381
  console.log();
19200
19382
  }
19201
19383
  const executedMap = new Map(executedMigrations.map((m) => [m.migration, m.executed_at]));
19202
- const statuses = migrationFiles.map((file2) => {
19203
- if (file2.includes("alter-") && file2.includes("-table")) {
19204
- return {
19205
- file: file2,
19206
- status: "transient"
19207
- };
19208
- }
19209
- const executedAt = executedMap.get(file2);
19210
- return {
19211
- file: file2,
19212
- status: executedAt ? "executed" : "pending",
19213
- executedAt
19214
- };
19215
- });
19384
+ const statuses = migrationFiles.map((file2) => ({
19385
+ file: file2,
19386
+ status: executedMap.has(file2) ? "executed" : "pending",
19387
+ executedAt: executedMap.get(file2)
19388
+ }));
19216
19389
  const executed = statuses.filter((s) => s.status === "executed");
19217
19390
  const pending = statuses.filter((s) => s.status === "pending");
19218
- const transient = statuses.filter((s) => s.status === "transient");
19219
19391
  console.log(`-- Total migrations: ${migrationFiles.length}`);
19220
19392
  console.log(`-- Executed: ${executed.length}`);
19221
19393
  console.log(`-- Pending: ${pending.length}`);
19222
- console.log(`-- Transient: ${transient.length}`);
19223
19394
  console.log();
19224
19395
  if (executed.length > 0) {
19225
19396
  console.log("\u2713 Executed Migrations:");
@@ -19235,13 +19406,6 @@ async function migrateStatus() {
19235
19406
  }
19236
19407
  console.log();
19237
19408
  }
19238
- if (transient.length > 0) {
19239
- console.log("\u26A1 Transient Migrations (ALTER TABLE - not tracked):");
19240
- for (const migration of transient) {
19241
- console.log(` - ${migration.file}`);
19242
- }
19243
- console.log();
19244
- }
19245
19409
  return statuses;
19246
19410
  } catch (err) {
19247
19411
  console.error("-- Failed to get migration status:", err);
@@ -19255,14 +19419,15 @@ var init_migrate_status = __esm(() => {
19255
19419
  init_config();
19256
19420
  init_drivers();
19257
19421
  init_src2();
19422
+ init_workspace();
19258
19423
  });
19259
19424
 
19260
19425
  // src/actions/model-show.ts
19261
19426
  import { readdirSync as readdirSync9 } from "fs";
19262
19427
  import { extname as extname2, join as join12 } from "path";
19263
- import process28 from "process";
19428
+ import process26 from "process";
19264
19429
  async function modelShow(modelName, options = {}) {
19265
- const dir = options.dir || join12(process28.cwd(), "app/Models");
19430
+ const dir = options.dir || join12(process26.cwd(), "app/Models");
19266
19431
  try {
19267
19432
  const files = readdirSync9(dir);
19268
19433
  const modelFile = files.find((f) => {
@@ -19476,9 +19641,9 @@ var init_query_explain_all = __esm(() => {
19476
19641
  // src/actions/relation-diagram.ts
19477
19642
  import { writeFileSync as writeFileSync12 } from "fs";
19478
19643
  import { join as join14 } from "path";
19479
- import process29 from "process";
19644
+ import process27 from "process";
19480
19645
  async function relationDiagram(options = {}) {
19481
- const dir = options.dir || join14(process29.cwd(), "app/Models");
19646
+ const dir = options.dir || join14(process27.cwd(), "app/Models");
19482
19647
  const format = options.format || "mermaid";
19483
19648
  try {
19484
19649
  const models = await loadModels({ modelsDir: dir });
@@ -19598,25 +19763,25 @@ var init_relation_diagram = __esm(() => {
19598
19763
  });
19599
19764
 
19600
19765
  // src/actions/seed.ts
19601
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19766
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
19602
19767
  import { dirname as dirname10, join as join15 } from "path";
19603
- import process30 from "process";
19604
- function findWorkspaceRoot4(startPath) {
19768
+ import process28 from "process";
19769
+ function findWorkspaceRoot3(startPath) {
19605
19770
  let currentPath = startPath;
19606
19771
  while (currentPath !== dirname10(currentPath)) {
19607
- if (existsSync19(join15(currentPath, "package.json"))) {
19772
+ if (existsSync20(join15(currentPath, "package.json"))) {
19608
19773
  return currentPath;
19609
19774
  }
19610
19775
  currentPath = dirname10(currentPath);
19611
19776
  }
19612
- return process30.cwd();
19777
+ return process28.cwd();
19613
19778
  }
19614
19779
  async function loadSeeders(seedersDir) {
19615
- if (!existsSync19(seedersDir)) {
19780
+ if (!existsSync20(seedersDir)) {
19616
19781
  console.log(`-- Seeders directory not found: ${seedersDir}`);
19617
19782
  return [];
19618
19783
  }
19619
- const files = readdirSync11(seedersDir);
19784
+ const files = readdirSync11(seedersDir).sort((a, b) => a.localeCompare(b));
19620
19785
  const seederFiles = files.filter((file2) => (file2.endsWith(".ts") || file2.endsWith(".js")) && file2 !== "index.ts" && file2 !== "index.js");
19621
19786
  const seeders = [];
19622
19787
  for (const file2 of seederFiles) {
@@ -19629,6 +19794,7 @@ async function loadSeeders(seedersDir) {
19629
19794
  if (instance && typeof instance.run === "function") {
19630
19795
  seeders.push({
19631
19796
  name: file2.replace(/\.(ts|js)$/, ""),
19797
+ className: String(SeederClass.name || ""),
19632
19798
  instance
19633
19799
  });
19634
19800
  }
@@ -19641,7 +19807,7 @@ async function loadSeeders(seedersDir) {
19641
19807
  return seeders;
19642
19808
  }
19643
19809
  async function runSeeders(config6 = {}) {
19644
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19810
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19645
19811
  const seedersDir = config6.seedersDir || join15(workspaceRoot, "database/seeders");
19646
19812
  const verbose = config6.verbose ?? true;
19647
19813
  if (verbose) {
@@ -19676,17 +19842,18 @@ async function runSeeders(config6 = {}) {
19676
19842
  }
19677
19843
  }
19678
19844
  async function runSeeder(className, options = {}) {
19679
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19845
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19680
19846
  const seedersDir = join15(workspaceRoot, "database/seeders");
19681
19847
  const verbose = options.verbose ?? true;
19682
19848
  if (verbose) {
19683
19849
  console.log(`-- Running seeder: ${className}`);
19684
19850
  }
19685
19851
  const seeders = await loadSeeders(seedersDir);
19686
- const seeder = seeders.find((s) => s.name === className);
19852
+ const seeder = seeders.find((s) => s.className === className) ?? seeders.find((s) => s.name === className);
19687
19853
  if (!seeder) {
19688
- console.error(`-- Seeder not found: ${className}`);
19689
- throw new Error(`Seeder not found: ${className}`);
19854
+ const known = seeders.map((s) => s.className || s.name).join(", ") || "none";
19855
+ console.error(`-- Seeder not found: ${className}. Available: ${known}`);
19856
+ throw new Error(`Seeder not found: ${className}. Available: ${known}`);
19690
19857
  }
19691
19858
  const qb = createQueryBuilder();
19692
19859
  try {
@@ -19700,9 +19867,9 @@ async function runSeeder(className, options = {}) {
19700
19867
  }
19701
19868
  }
19702
19869
  async function makeSeeder(name) {
19703
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19870
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19704
19871
  const seedersDir = join15(workspaceRoot, "database/seeders");
19705
- if (!existsSync19(seedersDir)) {
19872
+ if (!existsSync20(seedersDir)) {
19706
19873
  mkdirSync9(seedersDir, { recursive: true });
19707
19874
  console.log(`-- Created seeders directory: ${seedersDir}`);
19708
19875
  }
@@ -19710,7 +19877,7 @@ async function makeSeeder(name) {
19710
19877
  const className = `${baseName}Seeder`;
19711
19878
  const fileName = `${className}.ts`;
19712
19879
  const filePath = join15(seedersDir, fileName);
19713
- if (existsSync19(filePath)) {
19880
+ if (existsSync20(filePath)) {
19714
19881
  console.error(`-- Seeder already exists: ${filePath}`);
19715
19882
  throw new Error(`Seeder already exists: ${filePath}`);
19716
19883
  }
@@ -19764,7 +19931,7 @@ export default class ${className} extends Seeder {
19764
19931
  console.log(`-- \u2713 Created seeder: ${filePath}`);
19765
19932
  }
19766
19933
  async function freshDatabase(options = {}) {
19767
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
19934
+ const workspaceRoot = findWorkspaceRoot3(process28.cwd());
19768
19935
  const modelsDir = options.modelsDir || join15(workspaceRoot, "app/Models");
19769
19936
  const seedersDir = options.seedersDir || join15(workspaceRoot, "database/seeders");
19770
19937
  const verbose = options.verbose ?? true;
@@ -19824,22 +19991,22 @@ var init_unsafe = __esm(() => {
19824
19991
  });
19825
19992
 
19826
19993
  // src/actions/validate.ts
19827
- import { existsSync as existsSync20 } from "fs";
19994
+ import { existsSync as existsSync21 } from "fs";
19828
19995
  import { dirname as dirname11, join as join16 } from "path";
19829
- import process31 from "process";
19830
- function findWorkspaceRoot5(startPath) {
19996
+ import process29 from "process";
19997
+ function findWorkspaceRoot4(startPath) {
19831
19998
  let currentPath = startPath;
19832
19999
  while (currentPath !== dirname11(currentPath)) {
19833
- if (existsSync20(join16(currentPath, "package.json"))) {
20000
+ if (existsSync21(join16(currentPath, "package.json"))) {
19834
20001
  return currentPath;
19835
20002
  }
19836
20003
  currentPath = dirname11(currentPath);
19837
20004
  }
19838
- return process31.cwd();
20005
+ return process29.cwd();
19839
20006
  }
19840
20007
  async function validateSchema(dir) {
19841
20008
  if (!dir) {
19842
- dir = join16(findWorkspaceRoot5(process31.cwd()), "app/Models");
20009
+ dir = join16(findWorkspaceRoot4(process29.cwd()), "app/Models");
19843
20010
  }
19844
20011
  const dialect = config5.dialect || "postgres";
19845
20012
  console.log("-- Validating Schema");
@@ -25090,18 +25257,11 @@ function toSnakeCase(str) {
25090
25257
  snakeCaseCache.set(str, cached);
25091
25258
  return cached;
25092
25259
  }
25093
- function toTableName(modelName) {
25260
+ function toTableName2(modelName) {
25094
25261
  let cached = tableNameCache.get(modelName);
25095
25262
  if (cached !== undefined)
25096
25263
  return cached;
25097
- const snake = toSnakeCase(modelName);
25098
- if (snake.endsWith("y") && !snake.endsWith("ay") && !snake.endsWith("ey") && !snake.endsWith("oy") && !snake.endsWith("uy")) {
25099
- cached = snake.slice(0, -1) + "ies";
25100
- } else if (snake.endsWith("s") || snake.endsWith("x") || snake.endsWith("ch") || snake.endsWith("sh")) {
25101
- cached = `${snake}es`;
25102
- } else {
25103
- cached = `${snake}s`;
25104
- }
25264
+ cached = toTableName(modelName);
25105
25265
  tableNameCache.set(modelName, cached);
25106
25266
  return cached;
25107
25267
  }
@@ -25186,14 +25346,14 @@ function resolveRelation(definition, relationName) {
25186
25346
  const hasManyModel = findModelName(definition.hasMany);
25187
25347
  if (hasManyModel) {
25188
25348
  const relatedModel = getModelFromRegistry(hasManyModel);
25189
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasManyModel);
25349
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasManyModel);
25190
25350
  const foreignKey = toSnakeCase(parentName) + "_id";
25191
25351
  return { type: "hasMany", relatedModelName: hasManyModel, relatedTable, foreignKey, localKey: parentPk };
25192
25352
  }
25193
25353
  const hasOneModel = findModelName(definition.hasOne);
25194
25354
  if (hasOneModel) {
25195
25355
  const relatedModel = getModelFromRegistry(hasOneModel);
25196
- const relatedTable = relatedModel?.getTable?.() || toTableName(hasOneModel);
25356
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(hasOneModel);
25197
25357
  const foreignKey = toSnakeCase(parentName) + "_id";
25198
25358
  return { type: "hasOne", relatedModelName: hasOneModel, relatedTable, foreignKey, localKey: parentPk };
25199
25359
  }
@@ -25201,7 +25361,7 @@ function resolveRelation(definition, relationName) {
25201
25361
  if (belongsToEntry) {
25202
25362
  const belongsToModel = belongsToEntry.model;
25203
25363
  const relatedModel = getModelFromRegistry(belongsToModel);
25204
- const relatedTable = relatedModel?.getTable?.() || toTableName(belongsToModel);
25364
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(belongsToModel);
25205
25365
  const relatedPk = relatedModel?.getDefinition?.()?.primaryKey || "id";
25206
25366
  const foreignKey = belongsToEntry.foreignKey || `${toSnakeCase(belongsToModel)}_id`;
25207
25367
  return { type: "belongsTo", relatedModelName: belongsToModel, relatedTable, foreignKey, localKey: relatedPk };
@@ -25211,7 +25371,7 @@ function resolveRelation(definition, relationName) {
25211
25371
  const isConfig = typeof found.entry === "object";
25212
25372
  const relatedModelName = isConfig ? found.entry.model : found.entry;
25213
25373
  const relatedModel = getModelFromRegistry(relatedModelName);
25214
- const relatedTable = relatedModel?.getTable?.() || toTableName(relatedModelName);
25374
+ const relatedTable = relatedModel?.getTable?.() || toTableName2(relatedModelName);
25215
25375
  const config6 = isConfig ? found.entry : null;
25216
25376
  let pivotTable;
25217
25377
  let pivotModelName;
@@ -25223,7 +25383,7 @@ function resolveRelation(definition, relationName) {
25223
25383
  if (!throughModel) {
25224
25384
  throw new Error(`[orm] belongsToMany relation '${relationName}' on '${parentName}' references unknown through model '${config6.through}'. Make sure '${config6.through}' is registered.`);
25225
25385
  }
25226
- pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName(config6.through);
25386
+ pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName2(config6.through);
25227
25387
  } else {
25228
25388
  const a2 = singularizeWord(parentTable);
25229
25389
  const b2 = singularizeWord(relatedTable);
@@ -25274,10 +25434,10 @@ function resolveRelation(definition, relationName) {
25274
25434
  return null;
25275
25435
  const throughModel = getModelFromRegistry(throughModelName);
25276
25436
  const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
25277
- const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
25437
+ const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName2(throughModelName);
25278
25438
  const throughPk = throughDef?.primaryKey || "id";
25279
25439
  const targetModel = getModelFromRegistry(targetModelName);
25280
- const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
25440
+ const targetTable = targetModel?.getTable?.() || toTableName2(targetModelName);
25281
25441
  return {
25282
25442
  type,
25283
25443
  relatedModelName: targetModelName,
@@ -27874,7 +28034,7 @@ function buildDatabaseSchema(models) {
27874
28034
  for (const modelName of Object.keys(models)) {
27875
28035
  const rawModel = models[modelName];
27876
28036
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27877
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
28037
+ const table = tableNameFor(m2);
27878
28038
  const attrs = m2.attributes ?? {};
27879
28039
  const columns = {};
27880
28040
  for (const key of Object.keys(attrs))
@@ -27886,30 +28046,53 @@ function buildDatabaseSchema(models) {
27886
28046
  }
27887
28047
  return schema;
27888
28048
  }
28049
+ var init_factory = () => {};
27889
28050
 
27890
28051
  // src/loader.ts
27891
- import { readdirSync as readdirSync12, statSync as statSync5 } from "fs";
27892
- import { basename, extname as extname4 } from "path";
27893
- import process33 from "process";
27894
- async function loadModels(options) {
27895
- const cwd = options.cwd ?? process33.cwd();
27896
- const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
27897
- const result = {};
27898
- const entries = readdirSync12(dir);
28052
+ import { readdirSync as readdirSync12 } from "fs";
28053
+ import { basename, extname as extname4, isAbsolute as isAbsolute3, join as join17 } from "path";
28054
+ import process30 from "process";
28055
+ function isModelFile(name) {
28056
+ const ext = extname4(name);
28057
+ if (!MODEL_EXTENSIONS.has(ext))
28058
+ return false;
28059
+ const base = basename(name, ext);
28060
+ if (base.startsWith("_") || base === "index")
28061
+ return false;
28062
+ return !base.endsWith(".d");
28063
+ }
28064
+ function isModelDefinition(def) {
28065
+ if (!def || typeof def !== "object")
28066
+ return false;
28067
+ const d2 = def;
28068
+ return typeof d2.name === "string" || typeof d2.table === "string";
28069
+ }
28070
+ function collectModelFiles(dir) {
28071
+ const files = [];
28072
+ const entries = readdirSync12(dir, { withFileTypes: true }).sort((a2, b2) => a2.name.localeCompare(b2.name));
27899
28073
  for (const entry of entries) {
27900
- const full = `${dir}/${entry}`;
27901
- const st = statSync5(full);
27902
- if (st.isDirectory())
27903
- continue;
27904
- const ext = extname4(full);
27905
- if (![".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"].includes(ext))
28074
+ const full = join17(dir, entry.name);
28075
+ if (entry.isDirectory()) {
28076
+ files.push(...collectModelFiles(full));
27906
28077
  continue;
28078
+ }
28079
+ if (isModelFile(entry.name))
28080
+ files.push(full);
28081
+ }
28082
+ return files;
28083
+ }
28084
+ async function loadModels(options) {
28085
+ const cwd = options.cwd ?? process30.cwd();
28086
+ const dir = isAbsolute3(options.modelsDir) ? options.modelsDir : join17(cwd, options.modelsDir);
28087
+ const result = {};
28088
+ for (const full of collectModelFiles(dir)) {
27907
28089
  const cacheBuster = `?t=${Date.now()}-${Math.random().toString(36).slice(2)}`;
27908
28090
  const mod = await import(`${full}${cacheBuster}`);
27909
28091
  const rawDef = mod.default ?? mod;
27910
28092
  const def = rawDef.definition ?? rawDef.getDefinition?.() ?? rawDef;
27911
- const fileName = basename(entry, ext);
27912
- const name = def.name ?? fileName;
28093
+ if (!isModelDefinition(def))
28094
+ continue;
28095
+ const name = def.name ?? basename(full, extname4(full));
27913
28096
  result[name] = {
27914
28097
  ...def,
27915
28098
  name
@@ -27917,7 +28100,10 @@ async function loadModels(options) {
27917
28100
  }
27918
28101
  return result;
27919
28102
  }
27920
- var init_loader = () => {};
28103
+ var MODEL_EXTENSIONS;
28104
+ var init_loader = __esm(() => {
28105
+ MODEL_EXTENSIONS = new Set([".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]);
28106
+ });
27921
28107
 
27922
28108
  // src/meta.ts
27923
28109
  function buildSchemaMeta(models) {
@@ -27929,7 +28115,7 @@ function buildSchemaMeta(models) {
27929
28115
  for (const name of Object.keys(models)) {
27930
28116
  const rawModel = models[name];
27931
28117
  const m2 = rawModel.definition ?? rawModel.getDefinition?.() ?? rawModel;
27932
- const table = m2.table || `${String(m2.name).toLowerCase()}s`;
28118
+ const table = tableNameFor(m2);
27933
28119
  modelToTable[name] = table;
27934
28120
  tableToModel[table] = name;
27935
28121
  primaryKeys[table] = m2.primaryKey ?? "id";
@@ -28003,9 +28189,9 @@ function buildSchemaMeta(models) {
28003
28189
  var init_meta = () => {};
28004
28190
 
28005
28191
  // src/migrations.ts
28006
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync14 } from "fs";
28007
- import { dirname as dirname12, join as join17 } from "path";
28008
- import process35 from "process";
28192
+ import { existsSync as existsSync23, mkdirSync as mkdirSync10, readdirSync as readdirSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync14 } from "fs";
28193
+ import { dirname as dirname12, join as join18 } from "path";
28194
+ import process31 from "process";
28009
28195
  function info2(message) {
28010
28196
  if (config5.verbose)
28011
28197
  console.log(message);
@@ -28016,36 +28202,80 @@ function snakeCase(str) {
28016
28202
  function singularizeTable(table) {
28017
28203
  return singularizerFor(config5.relations?.singularizeStrategy)(table);
28018
28204
  }
28019
- function findWorkspaceRoot6(startPath) {
28205
+ function lastCreatedMigrationFiles() {
28206
+ return [...createdMigrationFiles];
28207
+ }
28208
+ function findWorkspaceRoot5(startPath) {
28020
28209
  let currentPath = startPath;
28021
28210
  while (currentPath !== dirname12(currentPath)) {
28022
- if (existsSync21(join17(currentPath, "package.json"))) {
28211
+ if (existsSync23(join18(currentPath, "package.json"))) {
28023
28212
  return currentPath;
28024
28213
  }
28025
28214
  currentPath = dirname12(currentPath);
28026
28215
  }
28027
- return process35.cwd();
28216
+ return process31.cwd();
28028
28217
  }
28029
28218
  function ensureSqlDirectory2() {
28030
- const workspaceRoot = findWorkspaceRoot6(process35.cwd());
28031
- const sqlDir = join17(workspaceRoot, "database", "migrations");
28032
- if (!existsSync21(sqlDir)) {
28219
+ const workspaceRoot = findWorkspaceRoot5(process31.cwd());
28220
+ const sqlDir = join18(workspaceRoot, "database", "migrations");
28221
+ if (!existsSync23(sqlDir)) {
28033
28222
  mkdirSync10(sqlDir, { recursive: true });
28034
28223
  info2(`-- Created SQL directory: ${sqlDir}`);
28035
28224
  }
28036
28225
  return sqlDir;
28037
28226
  }
28227
+ function isGeneratedMigrationSql(sql2) {
28228
+ return sql2.trimStart().startsWith(GENERATED_MARKER);
28229
+ }
28230
+ function migrationBody(sql2) {
28231
+ return sql2.split(`
28232
+ `).filter((line) => !line.trimStart().startsWith("--")).join(`
28233
+ `).replace(/\s+/g, " ").trim().replace(/;+$/, "");
28234
+ }
28235
+ function existingMigrationBodies(sqlDir) {
28236
+ const bodies = new Set;
28237
+ let files;
28238
+ try {
28239
+ files = readdirSync13(sqlDir).filter((f2) => f2.endsWith(".sql"));
28240
+ } catch {
28241
+ return bodies;
28242
+ }
28243
+ for (const file2 of files) {
28244
+ try {
28245
+ bodies.add(migrationBody(readFileSync6(join18(sqlDir, file2), "utf8")));
28246
+ } catch {}
28247
+ }
28248
+ return bodies;
28249
+ }
28038
28250
  function createMigrationFile(statement, fileName) {
28039
28251
  if (!statement)
28040
28252
  return false;
28041
28253
  const sqlDir = ensureSqlDirectory2();
28254
+ if (existingCorpusBodies === null)
28255
+ existingCorpusBodies = existingMigrationBodies(sqlDir);
28256
+ const body = migrationBody(statement);
28257
+ if (body.length > 0 && existingCorpusBodies.has(body)) {
28258
+ info2(`-- Migration already in the corpus, not writing it again: ${fileName}`);
28259
+ return false;
28260
+ }
28042
28261
  migrationCounter++;
28043
- const sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28044
- const fullFileName = `${sequence}-${fileName}.sql`;
28045
- const filePath = join17(sqlDir, fullFileName);
28046
- writeFileSync14(filePath, statement);
28262
+ let sequence = useDeterministicNames ? String(migrationCounter).padStart(10, "0") : String(Math.floor(Date.now() / 1000) + migrationCounter);
28263
+ let fullFileName = `${sequence}-${fileName}.sql`;
28264
+ if (!useDeterministicNames) {
28265
+ let next = Number(sequence);
28266
+ while (existsSync23(join18(sqlDir, fullFileName))) {
28267
+ next += 1;
28268
+ sequence = String(next);
28269
+ fullFileName = `${sequence}-${fileName}.sql`;
28270
+ }
28271
+ }
28272
+ const filePath = join18(sqlDir, fullFileName);
28273
+ writeFileSync14(filePath, `${GENERATED_MARKER}
28274
+ ${statement}`);
28047
28275
  info2(`-- Migration file created: ${fullFileName}`);
28048
28276
  migrationsCreatedCount++;
28277
+ createdMigrationFiles.push(fullFileName);
28278
+ existingCorpusBodies?.add(body);
28049
28279
  return true;
28050
28280
  }
28051
28281
  function guessTypeFromName(columnName) {
@@ -28220,7 +28450,7 @@ function buildMigrationPlan2(models, options) {
28220
28450
  const tables = [];
28221
28451
  for (const modelName of Object.keys(models)) {
28222
28452
  const model = models[modelName];
28223
- const table = model.table || `${String(model.name).toLowerCase()}s`;
28453
+ const table = tableNameFor(model);
28224
28454
  const primaryKey = model.primaryKey ?? "id";
28225
28455
  const _autoIncrement = model.autoIncrement !== false;
28226
28456
  const attrs = model.attributes ?? {};
@@ -28236,6 +28466,23 @@ function buildMigrationPlan2(models, options) {
28236
28466
  hasDefault: false
28237
28467
  });
28238
28468
  }
28469
+ const declaredFkTargets = new Map;
28470
+ const declaresBelongsTo = normalizeRelationList(model.belongsTo).length > 0;
28471
+ const columnNames = new Set(Object.keys(attrs).map((name) => snakeCase(name)));
28472
+ const polymorphicColumns = new Set;
28473
+ for (const column of columnNames) {
28474
+ if (!column.endsWith("_type"))
28475
+ continue;
28476
+ const idColumn = `${column.slice(0, -"_type".length)}_id`;
28477
+ if (columnNames.has(idColumn))
28478
+ polymorphicColumns.add(idColumn);
28479
+ }
28480
+ for (const rel of normalizeRelationList(model.belongsTo)) {
28481
+ const column = rel.foreignKey ?? `${snakeCase(rel.model)}_id`;
28482
+ const table2 = meta.modelToTable[rel.model];
28483
+ if (table2)
28484
+ declaredFkTargets.set(column, table2);
28485
+ }
28239
28486
  for (const attrName of Object.keys(attrs)) {
28240
28487
  const attr = attrs[attrName];
28241
28488
  const columnName = snakeCase(attrName);
@@ -28283,7 +28530,7 @@ function buildMigrationPlan2(models, options) {
28283
28530
  enumValues,
28284
28531
  maxLength: typeof maxLen === "number" && maxLen > 0 && maxLen <= 255 ? maxLen : undefined
28285
28532
  };
28286
- if (columnName.endsWith("_id") && attr.foreignKey !== false) {
28533
+ if (columnName.endsWith("_id") && attr.foreignKey !== false && !polymorphicColumns.has(columnName)) {
28287
28534
  if (typeof attr.foreignKey === "object" && attr.foreignKey !== null) {
28288
28535
  const fkConfig = attr.foreignKey;
28289
28536
  col.references = {
@@ -28295,9 +28542,10 @@ function buildMigrationPlan2(models, options) {
28295
28542
  if (fkConfig.nullable !== undefined)
28296
28543
  col.isNullable = fkConfig.nullable;
28297
28544
  } else {
28545
+ const declared = declaredFkTargets.get(columnName);
28298
28546
  const base = columnName.replace(/_id$/, "");
28299
28547
  const maybeModel = base.replace(/(^|_)([a-z])/g, (_2, __, c2) => c2.toUpperCase());
28300
- const refTable = meta.modelToTable[maybeModel];
28548
+ const refTable = declared ?? (declaresBelongsTo ? undefined : meta.modelToTable[maybeModel]);
28301
28549
  if (refTable) {
28302
28550
  const refPk = meta.primaryKeys[refTable] ?? "id";
28303
28551
  col.references = { table: refTable, column: refPk };
@@ -28312,9 +28560,7 @@ function buildMigrationPlan2(models, options) {
28312
28560
  if (columns.some((c2) => c2.name === fkColumnName))
28313
28561
  continue;
28314
28562
  const refTable = meta.modelToTable[rel.model];
28315
- if (!refTable)
28316
- continue;
28317
- const refPk = meta.primaryKeys[refTable] ?? "id";
28563
+ const refPk = refTable ? meta.primaryKeys[refTable] ?? "id" : undefined;
28318
28564
  columns.push({
28319
28565
  name: fkColumnName,
28320
28566
  type: "bigint",
@@ -28322,7 +28568,7 @@ function buildMigrationPlan2(models, options) {
28322
28568
  isUnique: false,
28323
28569
  isNullable: true,
28324
28570
  hasDefault: false,
28325
- references: { table: refTable, column: refPk, onDelete: rel.onDelete }
28571
+ references: refTable && refPk && !polymorphicColumns.has(fkColumnName) ? { table: refTable, column: refPk, onDelete: rel.onDelete } : undefined
28326
28572
  });
28327
28573
  }
28328
28574
  const traits = model.traits;
@@ -28403,7 +28649,7 @@ function buildMigrationPlan2(models, options) {
28403
28649
  for (const modelName of Object.keys(models)) {
28404
28650
  const model = models[modelName];
28405
28651
  const traits = model?.traits ?? {};
28406
- const modelTable = model.table || `${String(model.name).toLowerCase()}s`;
28652
+ const modelTable = tableNameFor(model);
28407
28653
  const singular = singularizeTable(modelTable);
28408
28654
  if (traits.likeable) {
28409
28655
  const opts = typeof traits.likeable === "object" ? traits.likeable : {};
@@ -28423,7 +28669,7 @@ function buildMigrationPlan2(models, options) {
28423
28669
  }
28424
28670
  if (traits.taggable) {
28425
28671
  ensureTable({
28426
- table: "taggable",
28672
+ table: "taggables",
28427
28673
  columns: [
28428
28674
  { name: "id", type: "bigint", isPrimaryKey: true, isUnique: false, isNullable: false, hasDefault: false },
28429
28675
  { name: "name", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
@@ -28431,37 +28677,61 @@ function buildMigrationPlan2(models, options) {
28431
28677
  { name: "description", type: "text", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false },
28432
28678
  { name: "order", type: "integer", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: 0 },
28433
28679
  { name: "is_active", type: "boolean", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: true },
28680
+ { name: "taggable_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false },
28681
+ { name: "taggable_type", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28682
+ { name: "created_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: "CURRENT_TIMESTAMP" },
28683
+ { name: "updated_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false }
28684
+ ],
28685
+ indexes: [
28686
+ { name: "taggables_target_index", columns: ["taggable_id", "taggable_type"], type: "index" },
28687
+ { name: "taggables_type_slug_unique", columns: ["taggable_type", "slug"], type: "unique" }
28688
+ ]
28689
+ });
28690
+ ensureTable({
28691
+ table: "taggable_models",
28692
+ columns: [
28693
+ { name: "id", type: "bigint", isPrimaryKey: true, isUnique: false, isNullable: false, hasDefault: false },
28694
+ { name: "tag_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false, references: { table: "taggables", column: "id" } },
28434
28695
  { name: "taggable_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28435
28696
  { name: "taggable_type", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28436
28697
  { name: "created_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: "CURRENT_TIMESTAMP" },
28437
28698
  { name: "updated_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false }
28438
28699
  ],
28439
- indexes: [{ name: "taggable_target_index", columns: ["taggable_id", "taggable_type"], type: "index" }]
28700
+ indexes: [
28701
+ { name: "taggable_models_owner_unique", columns: ["tag_id", "taggable_id", "taggable_type"], type: "unique" },
28702
+ { name: "taggable_models_target_index", columns: ["taggable_id", "taggable_type"], type: "index" }
28703
+ ]
28440
28704
  });
28441
28705
  }
28442
28706
  if (traits.categorizable) {
28443
28707
  ensureTable({
28444
- table: "categorizable",
28708
+ table: "categorizables",
28445
28709
  columns: [
28446
28710
  { name: "id", type: "bigint", isPrimaryKey: true, isUnique: false, isNullable: false, hasDefault: false },
28447
28711
  { name: "name", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28448
28712
  { name: "slug", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28449
28713
  { name: "description", type: "text", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false },
28714
+ { name: "is_active", type: "boolean", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: true },
28715
+ { name: "categorizable_type", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28450
28716
  { name: "created_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: "CURRENT_TIMESTAMP" },
28451
28717
  { name: "updated_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false }
28452
28718
  ],
28453
- indexes: [{ name: "categorizable_slug_unique", columns: ["slug"], type: "unique" }]
28719
+ indexes: [{ name: "categorizables_type_slug_unique", columns: ["categorizable_type", "slug"], type: "unique" }]
28454
28720
  });
28455
28721
  ensureTable({
28456
28722
  table: "categorizable_models",
28457
28723
  columns: [
28458
28724
  { name: "id", type: "bigint", isPrimaryKey: true, isUnique: false, isNullable: false, hasDefault: false },
28459
- { name: "category_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false, references: { table: "categorizable", column: "id" } },
28725
+ { name: "category_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false, references: { table: "categorizables", column: "id" } },
28460
28726
  { name: "categorizable_id", type: "bigint", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28461
28727
  { name: "categorizable_type", type: "string", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: false },
28462
- { name: "created_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: "CURRENT_TIMESTAMP" }
28728
+ { name: "created_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: false, hasDefault: true, defaultValue: "CURRENT_TIMESTAMP" },
28729
+ { name: "updated_at", type: "datetime", isPrimaryKey: false, isUnique: false, isNullable: true, hasDefault: false }
28463
28730
  ],
28464
- indexes: [{ name: "categorizable_models_target_index", columns: ["categorizable_id", "categorizable_type"], type: "index" }]
28731
+ indexes: [
28732
+ { name: "categorizable_models_owner_unique", columns: ["category_id", "categorizable_id", "categorizable_type"], type: "unique" },
28733
+ { name: "categorizable_models_target_index", columns: ["categorizable_id", "categorizable_type"], type: "index" }
28734
+ ]
28465
28735
  });
28466
28736
  }
28467
28737
  }
@@ -28470,7 +28740,7 @@ function buildMigrationPlan2(models, options) {
28470
28740
  const btm = model?.belongsToMany;
28471
28741
  if (!btm || typeof btm !== "object" || Array.isArray(btm))
28472
28742
  continue;
28473
- const parentTable = model.table || `${String(model.name).toLowerCase()}s`;
28743
+ const parentTable = tableNameFor(model);
28474
28744
  const parentSingular = singularizeTable(parentTable);
28475
28745
  for (const [relKey, value] of Object.entries(btm)) {
28476
28746
  const cfg = value;
@@ -28531,6 +28801,8 @@ function generateSql(plan, opts = {}) {
28531
28801
  migrationCounter = 0;
28532
28802
  migrationsCreatedCount = 0;
28533
28803
  migrationsUpdatedCount = 0;
28804
+ createdMigrationFiles = [];
28805
+ existingCorpusBodies = null;
28534
28806
  useDeterministicNames = true;
28535
28807
  const emit = opts.dryRun ? () => false : createMigrationFile;
28536
28808
  const statements = [];
@@ -28792,12 +29064,13 @@ function referencesAreDifferent(r1, r2) {
28792
29064
  const act = (a2) => (a2 ?? "no action").toLowerCase();
28793
29065
  return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
28794
29066
  }
28795
- function mapIndexesByKey(indexes) {
29067
+ function indexKey(index, table) {
29068
+ return `${index.type}:${qualifiedIndexName(table, index.name)}:${index.columns.join(",")}`;
29069
+ }
29070
+ function mapIndexesByKey(indexes, table) {
28796
29071
  const map = {};
28797
- for (const i2 of indexes) {
28798
- const key = `${i2.type}:${i2.name}:${i2.columns.join(",")}`;
28799
- map[key] = i2;
28800
- }
29072
+ for (const i2 of indexes)
29073
+ map[indexKey(i2, table)] = i2;
28801
29074
  return map;
28802
29075
  }
28803
29076
  function generateDiffOperations(previous, next, opts = {}) {
@@ -28812,10 +29085,13 @@ function generateDiffOperations(previous, next, opts = {}) {
28812
29085
  return { statements: statements2, operations: operations2 };
28813
29086
  }
28814
29087
  const applyRenames = opts.applyRenames !== false;
29088
+ const preserveUnknownColumns = opts.preserveUnknownColumns === true;
28815
29089
  const emit = opts.dryRun ? () => false : createMigrationFile;
28816
29090
  migrationCounter = 0;
28817
29091
  migrationsCreatedCount = 0;
28818
29092
  migrationsUpdatedCount = 0;
29093
+ createdMigrationFiles = [];
29094
+ existingCorpusBodies = null;
28819
29095
  useDeterministicNames = false;
28820
29096
  const chunks = [];
28821
29097
  const operations = [];
@@ -28894,42 +29170,52 @@ function generateDiffOperations(previous, next, opts = {}) {
28894
29170
  const prevCols = mapColumnsByName(prev.columns);
28895
29171
  const currCols = mapColumnsByName(curr.columns);
28896
29172
  for (const colName of Object.keys(currCols)) {
28897
- if (!prevCols[colName]) {
28898
- const c2 = currCols[colName];
28899
- if (c2.type === "enum" && c2.enumValues && c2.enumValues.length > 0) {
28900
- const enumTypeName = `${curr.table}_${c2.name}_type`;
28901
- c2.enumTypeName = enumTypeName;
28902
- if (!enumTypes.has(enumTypeName)) {
28903
- const createEnumStatement = driver.createEnumType(enumTypeName, c2.enumValues);
28904
- if (createEnumStatement) {
28905
- chunks.push(createEnumStatement);
28906
- operations.push({ kind: "create_enum", table: curr.table, column: c2.name, destructive: false, sql: createEnumStatement });
28907
- emit(createEnumStatement, `create-${enumTypeName}-enum`);
28908
- }
28909
- enumTypes.add(enumTypeName);
28910
- }
29173
+ const c2 = currCols[colName];
29174
+ if (c2.type !== "enum" || !c2.enumValues || c2.enumValues.length === 0)
29175
+ continue;
29176
+ const enumTypeName = `${curr.table}_${c2.name}_type`;
29177
+ c2.enumTypeName = enumTypeName;
29178
+ if (prevCols[colName])
29179
+ continue;
29180
+ if (!enumTypes.has(enumTypeName)) {
29181
+ const createEnumStatement = driver.createEnumType(enumTypeName, c2.enumValues);
29182
+ if (createEnumStatement) {
29183
+ chunks.push(createEnumStatement);
29184
+ operations.push({ kind: "create_enum", table: curr.table, column: c2.name, destructive: false, sql: createEnumStatement });
29185
+ emit(createEnumStatement, `create-${enumTypeName}-enum`);
28911
29186
  }
29187
+ enumTypes.add(enumTypeName);
28912
29188
  }
28913
29189
  }
28914
29190
  }
28915
29191
  for (const tableName of Object.keys(nextTables)) {
28916
29192
  const prev = prevTables[tableName];
28917
- const curr = nextTables[tableName];
29193
+ let curr = nextTables[tableName];
28918
29194
  if (!prev)
28919
29195
  continue;
28920
29196
  const prevCols = mapColumnsByName(prev.columns);
28921
- const currCols = mapColumnsByName(curr.columns);
28922
- const prevIdx = mapIndexesByKey(prev.indexes);
28923
- const currIdx = mapIndexesByKey(curr.indexes);
29197
+ const prevIdx = mapIndexesByKey(prev.indexes, prev.table);
29198
+ let currCols = mapColumnsByName(curr.columns);
29199
+ let currIdx = mapIndexesByKey(curr.indexes, curr.table);
28924
29200
  let renames = [];
28925
29201
  let removedCols = Object.keys(prevCols).filter((n2) => !currCols[n2]);
28926
29202
  let addedCols = Object.keys(currCols).filter((n2) => !prevCols[n2]);
28927
- if (applyRenames) {
29203
+ if (applyRenames && !preserveUnknownColumns) {
28928
29204
  const detected = detectColumnRenames(prevCols, currCols, dialect);
28929
29205
  renames = detected.renames;
28930
29206
  removedCols = detected.removed;
28931
29207
  addedCols = detected.added;
28932
29208
  }
29209
+ if (preserveUnknownColumns && removedCols.length > 0) {
29210
+ const preserved = removedCols.map((name) => prevCols[name]);
29211
+ const preservedNames = new Set(removedCols);
29212
+ const keptIndexes = prev.indexes.filter((idx) => idx.columns.some((c2) => preservedNames.has(c2)) && !currIdx[indexKey(idx)]);
29213
+ 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`);
29214
+ curr = { ...curr, columns: [...curr.columns, ...preserved], indexes: [...curr.indexes, ...keptIndexes] };
29215
+ currCols = mapColumnsByName(curr.columns);
29216
+ currIdx = mapIndexesByKey(curr.indexes, curr.table);
29217
+ removedCols = [];
29218
+ }
28933
29219
  const modifiedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && columnsAreDifferent(prevCols[n2], currCols[n2], dialect));
28934
29220
  const fkChangedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && referencesAreDifferent(prevCols[n2].references, currCols[n2].references));
28935
29221
  if (dialect === "sqlite") {
@@ -29077,11 +29363,13 @@ function generateDiffOperations(previous, next, opts = {}) {
29077
29363
  function generateDiffSql(previous, next, opts = {}) {
29078
29364
  return generateDiffOperations(previous, next, opts).statements;
29079
29365
  }
29080
- var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true;
29366
+ var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true, createdMigrationFiles, existingCorpusBodies = null, GENERATED_MARKER = "-- qb:generated";
29081
29367
  var init_migrations = __esm(() => {
29082
29368
  init_config();
29083
29369
  init_drivers();
29370
+ init_sqlite();
29084
29371
  init_meta();
29372
+ createdMigrationFiles = [];
29085
29373
  });
29086
29374
 
29087
29375
  // src/schema.ts
@@ -29115,6 +29403,7 @@ var init_src2 = __esm(() => {
29115
29403
  init_dynamodb_single_table();
29116
29404
  init_dynamodb_tooling_adapter();
29117
29405
  init_dynamodb();
29406
+ init_factory();
29118
29407
  init_loader();
29119
29408
  init_meta();
29120
29409
  init_migrations();
@@ -29130,9 +29419,11 @@ export {
29130
29419
  validateSchema,
29131
29420
  unsafe,
29132
29421
  unmarshallItem,
29422
+ toTableName,
29133
29423
  toModelInstance,
29134
29424
  toDynamoDBItem,
29135
29425
  tinker,
29426
+ tableNameFor,
29136
29427
  tableInfo,
29137
29428
  stripTrailingS,
29138
29429
  startConsole,
@@ -29151,6 +29442,7 @@ export {
29151
29442
  resolveSqlitePragmas,
29152
29443
  resolvePivot,
29153
29444
  resolveKeyPattern,
29445
+ resolveDialect,
29154
29446
  resetDatabase,
29155
29447
  resetConnection,
29156
29448
  relationDiagram,
@@ -29163,6 +29455,7 @@ export {
29163
29455
  dbOptimize as optimizeDatabase,
29164
29456
  modelShow,
29165
29457
  modelNameForTable,
29458
+ migrationBody,
29166
29459
  migrateStatus,
29167
29460
  migrateRollback,
29168
29461
  migrateModels,
@@ -29172,8 +29465,10 @@ export {
29172
29465
  makeSeeder,
29173
29466
  makeModel,
29174
29467
  loadModels,
29468
+ lastCreatedMigrationFiles,
29175
29469
  iterateAllPivots,
29176
29470
  isMysqlLike,
29471
+ isGeneratedMigrationSql,
29177
29472
  isDefinitionEqual,
29178
29473
  isBrowser,
29179
29474
  introspectDatabase,
@@ -29268,6 +29563,7 @@ export {
29268
29563
  MySQLDriver,
29269
29564
  Model,
29270
29565
  MIGRATIONS_TABLE,
29566
+ GENERATED_MARKER,
29271
29567
  EntityQueryBuilder,
29272
29568
  DynamoDBToolingAdapter,
29273
29569
  DynamoDBQueryBuilder,