bun-query-builder 0.1.62 → 0.2.0

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