bun-query-builder 0.1.25 → 0.1.26

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
@@ -11694,7 +11694,7 @@ function setConfig(userConfig) {
11694
11694
  Object.assign(_config, config5);
11695
11695
  }
11696
11696
  }
11697
- var defaultConfig4, config5, _config = null, _lastConfiguredDialect = null, _warnedDialectConflicts;
11697
+ var defaultConfig4, CONFIG_SINGLETON_KEY, config5, _config = null, _lastConfiguredDialect = null, _warnedDialectConflicts;
11698
11698
  var init_config = __esm(() => {
11699
11699
  init_dist();
11700
11700
  defaultConfig4 = {
@@ -11754,14 +11754,14 @@ var init_config = __esm(() => {
11754
11754
  defaultFilter: true
11755
11755
  }
11756
11756
  };
11757
- config5 = { ...defaultConfig4 };
11757
+ CONFIG_SINGLETON_KEY = Symbol.for("bun-query-builder.config");
11758
+ config5 = globalThis[CONFIG_SINGLETON_KEY] ??= { ...defaultConfig4 };
11758
11759
  _warnedDialectConflicts = new Set;
11759
11760
  });
11760
11761
 
11761
11762
  // src/db.ts
11762
11763
  var {SQL } = globalThis.Bun;
11763
11764
  import { Database } from "bun:sqlite";
11764
- import process19 from "process";
11765
11765
 
11766
11766
  class SQLiteWrapper {
11767
11767
  db;
@@ -11993,6 +11993,21 @@ function createConnectionString(dialect, dbConfig) {
11993
11993
  throw new Error(`Unsupported dialect: ${dialect}`);
11994
11994
  }
11995
11995
  }
11996
+ function resolvePoolOptions(pool) {
11997
+ if (!pool)
11998
+ return {};
11999
+ const out = {};
12000
+ const toSeconds = (ms) => Math.max(0, Math.round(ms / 1000));
12001
+ if (typeof pool.max === "number" && Number.isFinite(pool.max))
12002
+ out.max = pool.max;
12003
+ if (typeof pool.idleTimeoutMs === "number" && Number.isFinite(pool.idleTimeoutMs))
12004
+ out.idleTimeout = toSeconds(pool.idleTimeoutMs);
12005
+ if (typeof pool.acquireTimeoutMs === "number" && Number.isFinite(pool.acquireTimeoutMs))
12006
+ out.connectionTimeout = toSeconds(pool.acquireTimeoutMs);
12007
+ if (typeof pool.maxLifetimeMs === "number" && Number.isFinite(pool.maxLifetimeMs))
12008
+ out.maxLifetime = toSeconds(pool.maxLifetimeMs);
12009
+ return out;
12010
+ }
11996
12011
  function getBunSql() {
11997
12012
  const dialect = config5.dialect;
11998
12013
  const connectionString = createConnectionString(dialect, config5.database);
@@ -12000,14 +12015,8 @@ function getBunSql() {
12000
12015
  if (dialect === "sqlite") {
12001
12016
  return createSQLiteSQL(connectionString);
12002
12017
  }
12003
- const sql = new SQL(connectionString);
12004
- if (sql && typeof sql.catch === "function") {
12005
- sql.catch((error) => {
12006
- if (config5.verbose && !error.message.includes("database") && !error.message.includes("does not exist")) {
12007
- console.warn(`[query-builder] Database connection error: ${error.message}`);
12008
- }
12009
- });
12010
- }
12018
+ const poolOptions = resolvePoolOptions(config5.database.pool);
12019
+ const sql = Object.keys(poolOptions).length > 0 ? new SQL(connectionString, poolOptions) : new SQL(connectionString);
12011
12020
  return sql;
12012
12021
  } catch (error) {
12013
12022
  console.error(`[query-builder] Failed to create database connection for dialect '${dialect}': ${error.message}`);
@@ -12017,19 +12026,31 @@ function getBunSql() {
12017
12026
  throw error;
12018
12027
  }
12019
12028
  }
12029
+ function connectionSignature() {
12030
+ const d = config5.database;
12031
+ return JSON.stringify({
12032
+ dialect: config5.dialect,
12033
+ database: d.database,
12034
+ username: d.username,
12035
+ password: d.password,
12036
+ host: d.host,
12037
+ port: d.port,
12038
+ url: d.url,
12039
+ pool: resolvePoolOptions(d.pool)
12040
+ });
12041
+ }
12020
12042
  function getOrCreateBunSql(forceNew = false) {
12021
- const configChanged = _bunSqlInstance !== null && (_currentDialect !== config5.dialect || _currentDatabase !== config5.database.database);
12043
+ const signature = connectionSignature();
12044
+ const configChanged = _bunSqlInstance !== null && _currentSignature !== signature;
12022
12045
  if (forceNew || configChanged || !_bunSqlInstance) {
12023
12046
  _bunSqlInstance = getBunSql();
12024
- _currentDialect = config5.dialect;
12025
- _currentDatabase = config5.database.database;
12047
+ _currentSignature = signature;
12026
12048
  }
12027
12049
  return _bunSqlInstance;
12028
12050
  }
12029
12051
  function resetConnection() {
12030
12052
  _bunSqlInstance = null;
12031
- _currentDialect = null;
12032
- _currentDatabase = null;
12053
+ _currentSignature = null;
12033
12054
  }
12034
12055
  async function withFreshConnection(fn) {
12035
12056
  try {
@@ -12044,7 +12065,7 @@ async function withFreshConnection(fn) {
12044
12065
  }
12045
12066
  }
12046
12067
  function createLazyBunSql() {
12047
- return new Proxy({}, {
12068
+ return new Proxy(function lazyBunSql() {}, {
12048
12069
  get(_target, prop) {
12049
12070
  const sql = getOrCreateBunSql();
12050
12071
  const value = sql[prop];
@@ -12059,20 +12080,10 @@ function createLazyBunSql() {
12059
12080
  }
12060
12081
  });
12061
12082
  }
12062
- var _bunSqlInstance = null, _currentDialect = null, _currentDatabase = null, bunSql;
12083
+ var _bunSqlInstance = null, _currentSignature = null, bunSql;
12063
12084
  var init_db = __esm(() => {
12064
12085
  init_config();
12065
12086
  bunSql = createLazyBunSql();
12066
- if (typeof process19 !== "undefined" && process19.on) {
12067
- const existingHandler = process19.listeners("unhandledRejection").find((h) => h.name === "sqlConnectionErrorHandler");
12068
- if (!existingHandler) {
12069
- let sqlConnectionErrorHandler = function(reason) {
12070
- if (reason && (reason.message?.includes("database") || reason.message?.includes("does not exist") || reason.code === "ERR_POSTGRES_SERVER_ERROR" || reason.code === "3D000")) {}
12071
- };
12072
- Object.defineProperty(sqlConnectionErrorHandler, "name", { value: "sqlConnectionErrorHandler" });
12073
- process19.on("unhandledRejection", sqlConnectionErrorHandler);
12074
- }
12075
- }
12076
12087
  });
12077
12088
 
12078
12089
  // src/actions/benchmark.ts
@@ -12322,6 +12333,47 @@ function* iterateAllPivots(meta, options = {}) {
12322
12333
  function isRawExpression(expr) {
12323
12334
  return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
12324
12335
  }
12336
+ function quoteInsertIdent(id) {
12337
+ return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
12338
+ }
12339
+ function buildInsertClause(rows, startIndex = 1) {
12340
+ const cols = Object.keys(rows[0] ?? {});
12341
+ const params = [];
12342
+ let idx = startIndex;
12343
+ const tuples = rows.map((row) => {
12344
+ const phs = cols.map((c) => {
12345
+ params.push(row[c]);
12346
+ return getPlaceholder(idx++);
12347
+ });
12348
+ return `(${phs.join(", ")})`;
12349
+ });
12350
+ return {
12351
+ colsSql: cols.map(quoteInsertIdent).join(", "),
12352
+ valuesSql: tuples.join(", "),
12353
+ params,
12354
+ nextIndex: idx
12355
+ };
12356
+ }
12357
+ function hasSlowQueryHook(h) {
12358
+ return Boolean(h && (h.onSlowQuery || h.slowQueryThresholdMs != null && h.slowQueryThresholdMs >= 0));
12359
+ }
12360
+ function renderSelectColumn(col) {
12361
+ if (typeof col === "string")
12362
+ return col;
12363
+ if (isRawExpression(col))
12364
+ return col.raw;
12365
+ if (col && typeof col === "object") {
12366
+ const anyCol = col;
12367
+ if (typeof anyCol.raw === "function")
12368
+ return String(anyCol.raw());
12369
+ if (typeof anyCol.sql === "string")
12370
+ return anyCol.sql;
12371
+ const str = String(col);
12372
+ if (str !== "[object Object]")
12373
+ return str;
12374
+ }
12375
+ throw new TypeError(`[query-builder] select(): unsupported column ${String(col)} \u2014 pass a column name, a string[], or a SQL fragment (e.g. sql\`count(*) as c\`)`);
12376
+ }
12325
12377
  function validateIdentifier(name, context) {
12326
12378
  if (!SQL_PATTERNS.IDENTIFIER.test(name)) {
12327
12379
  const contextMsg = context ? ` in ${context}` : "";
@@ -12536,20 +12588,35 @@ function createQueryBuilder(state) {
12536
12588
  config5.debug.captureText = prev;
12537
12589
  return s;
12538
12590
  }
12591
+ function computeParams(q) {
12592
+ if (!q || typeof q !== "object")
12593
+ return;
12594
+ if (Array.isArray(q.values))
12595
+ return q.values;
12596
+ if (Array.isArray(q.parameters))
12597
+ return q.parameters;
12598
+ if (Array.isArray(q.params))
12599
+ return q.params;
12600
+ return;
12601
+ }
12539
12602
  function runWithHooks(q, kind, opts) {
12540
12603
  const hooks = config5.hooks;
12541
- const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan);
12604
+ const slowMs = hooks?.slowQueryThresholdMs;
12605
+ const slowEnabled = slowMs != null && slowMs >= 0;
12606
+ const hasSlowQuery = Boolean(hooks?.onSlowQuery || slowEnabled);
12607
+ const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hasSlowQuery);
12542
12608
  const hasTimeoutOrSignal = opts?.timeoutMs && opts.timeoutMs > 0 || opts?.signal;
12543
12609
  if (!hasHooks && !hasTimeoutOrSignal) {
12544
12610
  return q.execute();
12545
12611
  }
12546
12612
  const text = computeSqlText(q);
12613
+ const params = computeParams(q);
12547
12614
  const startAt = Date.now();
12548
12615
  let span;
12549
12616
  try {
12550
- hooks?.onQueryStart?.({ sql: text, kind });
12617
+ hooks?.onQueryStart?.({ sql: text, params, kind });
12551
12618
  if (hooks?.startSpan)
12552
- span = hooks.startSpan({ sql: text, kind });
12619
+ span = hooks.startSpan({ sql: text, params, kind });
12553
12620
  } catch {}
12554
12621
  let finished = false;
12555
12622
  const finish = (err, rowCount) => {
@@ -12559,9 +12626,15 @@ function createQueryBuilder(state) {
12559
12626
  const durationMs = Date.now() - startAt;
12560
12627
  try {
12561
12628
  if (err) {
12562
- hooks?.onQueryError?.({ sql: text, error: err, durationMs, kind });
12629
+ hooks?.onQueryError?.({ sql: text, params, error: err, durationMs, kind });
12563
12630
  } else {
12564
- hooks?.onQueryEnd?.({ sql: text, durationMs, rowCount, kind });
12631
+ hooks?.onQueryEnd?.({ sql: text, params, durationMs, rowCount, kind });
12632
+ if (slowEnabled && durationMs >= slowMs) {
12633
+ if (hooks?.onSlowQuery)
12634
+ hooks.onSlowQuery({ sql: text, params, durationMs, kind });
12635
+ else
12636
+ console.warn(`[query-builder] slow query (${durationMs}ms >= ${slowMs}ms): ${text}`);
12637
+ }
12565
12638
  }
12566
12639
  } catch {}
12567
12640
  try {
@@ -12641,6 +12714,36 @@ function createQueryBuilder(state) {
12641
12714
  const p = hasWhere ? prefix : "WHERE";
12642
12715
  text = `${text} ${p} ${clause}`;
12643
12716
  };
12717
+ const appendSetOp = (op, other) => {
12718
+ const st = other.__rawState?.();
12719
+ if (st) {
12720
+ const offset = whereParams.length;
12721
+ const otherSql = config5.dialect === "postgres" ? st.sql.replace(/\$(\d+)/g, (_m, n) => `$${Number(n) + offset}`) : st.sql;
12722
+ text += ` ${op} ${otherSql}`;
12723
+ whereParams.push(...st.params);
12724
+ } else {
12725
+ text += ` ${op} ${String(other.toSQL())}`;
12726
+ }
12727
+ built = null;
12728
+ };
12729
+ const insertJoin = (joinClause) => {
12730
+ const re = /\(|\)|\b(?:WHERE|GROUP BY|HAVING|ORDER BY|LIMIT|OFFSET|UNION)\b/gi;
12731
+ let depth = 0;
12732
+ let cut = -1;
12733
+ let mm;
12734
+ while (mm = re.exec(text)) {
12735
+ if (mm[0] === "(") {
12736
+ depth++;
12737
+ } else if (mm[0] === ")") {
12738
+ depth = Math.max(0, depth - 1);
12739
+ } else if (depth === 0) {
12740
+ cut = mm.index;
12741
+ break;
12742
+ }
12743
+ }
12744
+ text = cut >= 0 ? `${text.slice(0, cut)}${joinClause} ${text.slice(cut)}` : `${text} ${joinClause}`;
12745
+ built = null;
12746
+ };
12644
12747
  const joinedTables = new Set;
12645
12748
  let timeoutMs;
12646
12749
  let abortSignal;
@@ -12725,6 +12828,18 @@ function createQueryBuilder(state) {
12725
12828
  }
12726
12829
  }
12727
12830
  };
12831
+ const buildOverClause = (partitionBy, orderBy) => {
12832
+ const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
12833
+ const parts = [];
12834
+ if (cols.length)
12835
+ parts.push(`PARTITION BY ${cols.join(", ")}`);
12836
+ if (orderBy && orderBy.length)
12837
+ parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
12838
+ return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
12839
+ };
12840
+ const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
12841
+ addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
12842
+ };
12728
12843
  function assertSafeWhereOperator(op, context) {
12729
12844
  if (typeof op !== "string")
12730
12845
  throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
@@ -12864,6 +12979,46 @@ function createQueryBuilder(state) {
12864
12979
  validateIdentifier(fkA, "withCount (foreign key)");
12865
12980
  return `(SELECT COUNT(*) FROM ${pivot} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
12866
12981
  };
12982
+ const applyRelationAggregate = (fn, relation, column) => {
12983
+ if (!meta)
12984
+ return;
12985
+ validateIdentifier(column, `with${fn[0]}${fn.slice(1).toLowerCase()} (column)`);
12986
+ const parentTable = String(table);
12987
+ const rels = meta.relations?.[parentTable];
12988
+ if (!rels)
12989
+ return;
12990
+ const found = Object.entries(rels).find(([_t, relMap2]) => relMap2 && typeof relMap2 === "object" && (relation in relMap2));
12991
+ if (!found)
12992
+ return;
12993
+ const [type, relMap] = found;
12994
+ const entry = relMap[relation];
12995
+ const targetModel = typeof entry === "string" ? entry : entry?.model || entry?.target || entry;
12996
+ const targetTable = meta.modelToTable[targetModel] || targetModel;
12997
+ const pk = meta.primaryKeys[parentTable] ?? "id";
12998
+ validateIdentifier(targetTable, `with${fn} (target table)`);
12999
+ const aggExpr = `${fn}(${targetTable}.${column})`;
13000
+ let sub;
13001
+ if (type === "hasMany" || type === "hasOne") {
13002
+ const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
13003
+ validateIdentifier(fk, `with${fn} (foreign key)`);
13004
+ sub = `(SELECT ${aggExpr} FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk})`;
13005
+ } else if (type === "belongsToMany") {
13006
+ const resolved = meta ? resolvePivot(meta, parentTable, relation, { singularize, models: meta.models }) : null;
13007
+ const a = singularize(parentTable);
13008
+ const b = singularize(targetTable);
13009
+ const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
13010
+ const fkA = resolved?.fkParent ?? `${a}_id`;
13011
+ const fkB = resolved?.fkRelated ?? `${b}_id`;
13012
+ const targetPk = meta.primaryKeys[targetTable] ?? "id";
13013
+ validateIdentifier(pivot, `with${fn} (pivot table)`);
13014
+ validateIdentifier(fkA, `with${fn} (foreign key)`);
13015
+ validateIdentifier(fkB, `with${fn} (related key)`);
13016
+ sub = `(SELECT ${aggExpr} FROM ${pivot} JOIN ${targetTable} ON ${targetTable}.${targetPk} = ${pivot}.${fkB} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
13017
+ } else {
13018
+ return;
13019
+ }
13020
+ addToSelectClause(`${sub} AS ${relation}_${fn.toLowerCase()}_${column}`);
13021
+ };
12867
13022
  const applyPivotColumnsToQuery = () => {
12868
13023
  if (pivotColumns.size === 0)
12869
13024
  return;
@@ -12958,6 +13113,52 @@ function createQueryBuilder(state) {
12958
13113
  built = null;
12959
13114
  return this;
12960
13115
  },
13116
+ over(expression, alias, opts = {}) {
13117
+ addWindowFunction(expression, alias, opts.partitionBy, opts.orderBy);
13118
+ return this;
13119
+ },
13120
+ lag(column, opts = {}) {
13121
+ const args = [column, String(opts.offset ?? 1)];
13122
+ if (opts.defaultValue !== undefined)
13123
+ args.push(String(opts.defaultValue));
13124
+ addWindowFunction(`LAG(${args.join(", ")})`, opts.alias ?? `${column}_lag`, opts.partitionBy, opts.orderBy);
13125
+ return this;
13126
+ },
13127
+ lead(column, opts = {}) {
13128
+ const args = [column, String(opts.offset ?? 1)];
13129
+ if (opts.defaultValue !== undefined)
13130
+ args.push(String(opts.defaultValue));
13131
+ addWindowFunction(`LEAD(${args.join(", ")})`, opts.alias ?? `${column}_lead`, opts.partitionBy, opts.orderBy);
13132
+ return this;
13133
+ },
13134
+ sumOver(column, opts = {}) {
13135
+ addWindowFunction(`SUM(${column})`, opts.alias ?? `${column}_sum`, opts.partitionBy, opts.orderBy);
13136
+ return this;
13137
+ },
13138
+ avgOver(column, opts = {}) {
13139
+ addWindowFunction(`AVG(${column})`, opts.alias ?? `${column}_avg`, opts.partitionBy, opts.orderBy);
13140
+ return this;
13141
+ },
13142
+ countOver(column = "*", opts = {}) {
13143
+ addWindowFunction(`COUNT(${column})`, opts.alias ?? "count_over", opts.partitionBy, opts.orderBy);
13144
+ return this;
13145
+ },
13146
+ minOver(column, opts = {}) {
13147
+ addWindowFunction(`MIN(${column})`, opts.alias ?? `${column}_min`, opts.partitionBy, opts.orderBy);
13148
+ return this;
13149
+ },
13150
+ maxOver(column, opts = {}) {
13151
+ addWindowFunction(`MAX(${column})`, opts.alias ?? `${column}_max`, opts.partitionBy, opts.orderBy);
13152
+ return this;
13153
+ },
13154
+ firstValue(column, opts = {}) {
13155
+ addWindowFunction(`FIRST_VALUE(${column})`, opts.alias ?? `${column}_first`, opts.partitionBy, opts.orderBy);
13156
+ return this;
13157
+ },
13158
+ lastValue(column, opts = {}) {
13159
+ addWindowFunction(`LAST_VALUE(${column})`, opts.alias ?? `${column}_last`, opts.partitionBy, opts.orderBy);
13160
+ return this;
13161
+ },
12961
13162
  selectAll() {
12962
13163
  return this;
12963
13164
  },
@@ -12967,22 +13168,24 @@ function createQueryBuilder(state) {
12967
13168
  const cols = Array.isArray(columns2) ? columns2 : [columns2];
12968
13169
  if (cols.length === 0)
12969
13170
  return this;
13171
+ const rendered = cols.map(renderSelectColumn);
12970
13172
  const fromIndex = text.indexOf(" FROM ");
12971
13173
  if (fromIndex !== -1) {
12972
- text = `SELECT ${cols.join(", ")}${text.substring(fromIndex)}`;
13174
+ text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
12973
13175
  } else {
12974
- text = `SELECT ${cols.join(", ")} FROM ${table}`;
13176
+ text = `SELECT ${rendered.join(", ")} FROM ${table}`;
12975
13177
  }
12976
13178
  return this;
12977
13179
  },
12978
13180
  addSelect(...columns2) {
12979
13181
  if (!columns2.length)
12980
13182
  return this;
13183
+ const rendered = columns2.map(renderSelectColumn);
12981
13184
  const fromIdx = text.indexOf(" FROM ");
12982
13185
  if (fromIdx !== -1) {
12983
- text = `${text.substring(0, fromIdx)}, ${columns2.join(", ")}${text.substring(fromIdx)}`;
13186
+ text = `${text.substring(0, fromIdx)}, ${rendered.join(", ")}${text.substring(fromIdx)}`;
12984
13187
  } else {
12985
- text += `, ${columns2.join(", ")}`;
13188
+ text += `, ${rendered.join(", ")}`;
12986
13189
  }
12987
13190
  built = null;
12988
13191
  return this;
@@ -13399,6 +13602,22 @@ function createQueryBuilder(state) {
13399
13602
  }
13400
13603
  return this;
13401
13604
  },
13605
+ withSum(relation, column) {
13606
+ applyRelationAggregate("SUM", relation, column);
13607
+ return this;
13608
+ },
13609
+ withAvg(relation, column) {
13610
+ applyRelationAggregate("AVG", relation, column);
13611
+ return this;
13612
+ },
13613
+ withMax(relation, column) {
13614
+ applyRelationAggregate("MAX", relation, column);
13615
+ return this;
13616
+ },
13617
+ withMin(relation, column) {
13618
+ applyRelationAggregate("MIN", relation, column);
13619
+ return this;
13620
+ },
13402
13621
  applyPivotColumns() {
13403
13622
  applyPivotColumnsToQuery();
13404
13623
  return this;
@@ -13610,8 +13829,9 @@ function createQueryBuilder(state) {
13610
13829
  },
13611
13830
  whereBetween(column, start, end) {
13612
13831
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13832
+ const i = whereParams.length + 1;
13833
+ text = `${text} ${keyword} ${String(column)} BETWEEN ${getPlaceholder(i)} AND ${getPlaceholder(i + 1)}`;
13613
13834
  whereParams.push(start, end);
13614
- text = `${text} ${keyword} ${String(column)} BETWEEN ? AND ?`;
13615
13835
  built = null;
13616
13836
  return this;
13617
13837
  },
@@ -13623,9 +13843,30 @@ function createQueryBuilder(state) {
13623
13843
  },
13624
13844
  whereJsonContains(column, json) {
13625
13845
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13846
+ const dialect = config5.dialect;
13626
13847
  const idx = whereParams.length + 1;
13627
- text += ` ${keyword} ${column} @> ${getPlaceholder(idx)}`;
13628
- whereParams.push(JSON.stringify(json));
13848
+ if (dialect === "postgres") {
13849
+ if (config5.sql?.jsonContainsMode === "function")
13850
+ text += ` ${keyword} jsonb_contains(${column}, ${getPlaceholder(idx)})`;
13851
+ else
13852
+ text += ` ${keyword} ${column} @> ${getPlaceholder(idx)}`;
13853
+ whereParams.push(JSON.stringify(json));
13854
+ } else if (dialect === "mysql") {
13855
+ text += ` ${keyword} JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`;
13856
+ whereParams.push(JSON.stringify(json));
13857
+ } else {
13858
+ if (Array.isArray(json)) {
13859
+ const conds = json.map((_, i) => `EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${getPlaceholder(idx + i)})`);
13860
+ text += ` ${keyword} (${conds.join(" AND ")})`;
13861
+ for (const v of json)
13862
+ whereParams.push(v);
13863
+ } else if (json !== null && typeof json === "object") {
13864
+ throw new Error("[query-builder] whereJsonContains: object containment is not supported on SQLite \u2014 pass a scalar or array, or use whereJsonPath.");
13865
+ } else {
13866
+ text += ` ${keyword} EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${getPlaceholder(idx)})`;
13867
+ whereParams.push(json);
13868
+ }
13869
+ }
13629
13870
  built = null;
13630
13871
  return this;
13631
13872
  },
@@ -13652,69 +13893,85 @@ function createQueryBuilder(state) {
13652
13893
  whereLike(column, pattern, caseSensitive = false) {
13653
13894
  const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13654
13895
  built = sql`${ensureBuilt()} WHERE ${expr}`;
13655
- addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
13896
+ const ph = getPlaceholder(whereParams.length + 1);
13897
+ addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13898
+ whereParams.push(pattern);
13656
13899
  return this;
13657
13900
  },
13658
13901
  whereILike(column, pattern) {
13902
+ const ph = getPlaceholder(whereParams.length + 1);
13659
13903
  if (config5.dialect === "postgres") {
13660
13904
  built = sql`${ensureBuilt()} WHERE ${sql(String(column))} ILIKE ${pattern}`;
13661
- addWhereText("WHERE", `${String(column)} ILIKE ?`);
13905
+ addWhereText("WHERE", `${String(column)} ILIKE ${ph}`);
13662
13906
  } else {
13663
13907
  const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13664
13908
  built = sql`${ensureBuilt()} WHERE ${expr}`;
13665
- addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(?)`);
13909
+ addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
13666
13910
  }
13911
+ whereParams.push(pattern);
13667
13912
  return this;
13668
13913
  },
13669
13914
  orWhereLike(column, pattern, caseSensitive = false) {
13670
13915
  const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13671
13916
  built = sql`${ensureBuilt()} OR ${expr}`;
13672
- addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
13917
+ const ph = getPlaceholder(whereParams.length + 1);
13918
+ addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13919
+ whereParams.push(pattern);
13673
13920
  return this;
13674
13921
  },
13675
13922
  orWhereILike(column, pattern) {
13923
+ const ph = getPlaceholder(whereParams.length + 1);
13676
13924
  if (config5.dialect === "postgres") {
13677
13925
  built = sql`${ensureBuilt()} OR ${sql(String(column))} ILIKE ${pattern}`;
13678
- addWhereText("OR", `${String(column)} ILIKE ?`);
13926
+ addWhereText("OR", `${String(column)} ILIKE ${ph}`);
13679
13927
  } else {
13680
13928
  const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13681
13929
  built = sql`${ensureBuilt()} OR ${expr}`;
13682
- addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(?)`);
13930
+ addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
13683
13931
  }
13932
+ whereParams.push(pattern);
13684
13933
  return this;
13685
13934
  },
13686
13935
  whereNotLike(column, pattern, caseSensitive = false) {
13687
13936
  const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
13688
13937
  built = sql`${ensureBuilt()} WHERE ${expr}`;
13689
- addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
13938
+ const ph = getPlaceholder(whereParams.length + 1);
13939
+ addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13940
+ whereParams.push(pattern);
13690
13941
  return this;
13691
13942
  },
13692
13943
  whereNotILike(column, pattern) {
13944
+ const ph = getPlaceholder(whereParams.length + 1);
13693
13945
  if (config5.dialect === "postgres") {
13694
13946
  built = sql`${ensureBuilt()} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
13695
- addWhereText("WHERE", `${String(column)} NOT ILIKE ?`);
13947
+ addWhereText("WHERE", `${String(column)} NOT ILIKE ${ph}`);
13696
13948
  } else {
13697
13949
  const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
13698
13950
  built = sql`${ensureBuilt()} WHERE ${expr}`;
13699
- addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(?)`);
13951
+ addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
13700
13952
  }
13953
+ whereParams.push(pattern);
13701
13954
  return this;
13702
13955
  },
13703
13956
  orWhereNotLike(column, pattern, caseSensitive = false) {
13704
13957
  const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
13705
13958
  built = sql`${ensureBuilt()} OR ${expr}`;
13706
- addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
13959
+ const ph = getPlaceholder(whereParams.length + 1);
13960
+ addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13961
+ whereParams.push(pattern);
13707
13962
  return this;
13708
13963
  },
13709
13964
  orWhereNotILike(column, pattern) {
13965
+ const ph = getPlaceholder(whereParams.length + 1);
13710
13966
  if (config5.dialect === "postgres") {
13711
13967
  built = sql`${ensureBuilt()} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
13712
- addWhereText("OR", `${String(column)} NOT ILIKE ?`);
13968
+ addWhereText("OR", `${String(column)} NOT ILIKE ${ph}`);
13713
13969
  } else {
13714
13970
  const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
13715
13971
  built = sql`${ensureBuilt()} OR ${expr}`;
13716
- addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(?)`);
13972
+ addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
13717
13973
  }
13974
+ whereParams.push(pattern);
13718
13975
  return this;
13719
13976
  },
13720
13977
  whereAny(cols, op, value) {
@@ -13755,7 +14012,8 @@ function createQueryBuilder(state) {
13755
14012
  },
13756
14013
  whereNotBetween(column, start, end) {
13757
14014
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13758
- text += ` ${keyword} ${column} NOT BETWEEN ? AND ?`;
14015
+ const i = whereParams.length + 1;
14016
+ text += ` ${keyword} ${column} NOT BETWEEN ${getPlaceholder(i)} AND ${getPlaceholder(i + 1)}`;
13759
14017
  whereParams.push(start, end);
13760
14018
  built = null;
13761
14019
  return this;
@@ -14023,7 +14281,7 @@ function createQueryBuilder(state) {
14023
14281
  return this;
14024
14282
  },
14025
14283
  join(table2, onLeft, operator, onRight) {
14026
- text = `${text} JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`;
14284
+ insertJoin(`JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
14027
14285
  joinedTables.add(table2);
14028
14286
  return this;
14029
14287
  },
@@ -14032,44 +14290,37 @@ function createQueryBuilder(state) {
14032
14290
  validateQualifiedIdentifier(onLeft, "joinSub(onLeft)");
14033
14291
  validateQualifiedIdentifier(onRight, "joinSub(onRight)");
14034
14292
  assertSafeWhereOperator(operator, "joinSub(operator)");
14035
- text += ` JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`;
14036
- built = null;
14293
+ insertJoin(`JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`);
14037
14294
  joinedTables.add(alias);
14038
14295
  return this;
14039
14296
  },
14040
14297
  innerJoin(table2, onLeft, operator, onRight) {
14041
- text = `${text} INNER JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`;
14042
- built = null;
14298
+ insertJoin(`INNER JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
14043
14299
  joinedTables.add(table2);
14044
14300
  return this;
14045
14301
  },
14046
14302
  leftJoin(table2, onLeft, operator, onRight) {
14047
- text = `${text} LEFT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`;
14048
- built = null;
14303
+ insertJoin(`LEFT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
14049
14304
  joinedTables.add(table2);
14050
14305
  return this;
14051
14306
  },
14052
14307
  leftJoinSub(sub, alias, onLeft, operator, onRight) {
14053
- text += ` LEFT JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`;
14054
- built = null;
14308
+ insertJoin(`LEFT JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`);
14055
14309
  joinedTables.add(alias);
14056
14310
  return this;
14057
14311
  },
14058
14312
  rightJoin(table2, onLeft, operator, onRight) {
14059
- text = `${text} RIGHT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`;
14060
- built = null;
14313
+ insertJoin(`RIGHT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
14061
14314
  joinedTables.add(table2);
14062
14315
  return this;
14063
14316
  },
14064
14317
  crossJoin(table2) {
14065
- text = `${text} CROSS JOIN ${table2}`;
14066
- built = null;
14318
+ insertJoin(`CROSS JOIN ${table2}`);
14067
14319
  joinedTables.add(table2);
14068
14320
  return this;
14069
14321
  },
14070
14322
  crossJoinSub(sub, alias) {
14071
- text += ` CROSS JOIN (${String(sub.toSQL())}) AS ${alias}`;
14072
- built = null;
14323
+ insertJoin(`CROSS JOIN (${String(sub.toSQL())}) AS ${alias}`);
14073
14324
  joinedTables.add(alias);
14074
14325
  return this;
14075
14326
  },
@@ -14122,9 +14373,10 @@ function createQueryBuilder(state) {
14122
14373
  return this;
14123
14374
  },
14124
14375
  having(expr) {
14376
+ const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14125
14377
  if (Array.isArray(expr)) {
14126
14378
  const paramIdx = whereParams.length + 1;
14127
- text = `${text} HAVING ${expr[0]} ${expr[1]} ${getPlaceholder(paramIdx)}`;
14379
+ text = `${text} ${kw} ${expr[0]} ${expr[1]} ${getPlaceholder(paramIdx)}`;
14128
14380
  whereParams.push(expr[2]);
14129
14381
  built = null;
14130
14382
  } else if (expr && typeof expr === "object" && !("raw" in expr)) {
@@ -14138,18 +14390,19 @@ function createQueryBuilder(state) {
14138
14390
  conditions[i] = `${key} = ${getPlaceholder(baseIdx + i + 1)}`;
14139
14391
  whereParams.push(expr[key]);
14140
14392
  }
14141
- text = `${text} HAVING ${conditions.join(" AND ")}`;
14393
+ text = `${text} ${kw} ${conditions.join(" AND ")}`;
14142
14394
  built = null;
14143
14395
  }
14144
14396
  } else if (expr && typeof expr.raw !== "undefined") {
14145
- text += ` HAVING ${expr.raw}`;
14397
+ text += ` ${kw} ${expr.raw}`;
14146
14398
  built = null;
14147
14399
  }
14148
14400
  return this;
14149
14401
  },
14150
14402
  havingRaw(fragment) {
14151
14403
  assertSqlFragment(fragment, "havingRaw(fragment)");
14152
- text += ` HAVING ${String(fragment)}`;
14404
+ const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14405
+ text += ` ${kw} ${String(fragment)}`;
14153
14406
  built = null;
14154
14407
  return this;
14155
14408
  },
@@ -14160,13 +14413,27 @@ function createQueryBuilder(state) {
14160
14413
  return this;
14161
14414
  },
14162
14415
  union(other) {
14163
- text += ` UNION ${String(other.toSQL())}`;
14164
- built = null;
14416
+ appendSetOp("UNION", other);
14165
14417
  return this;
14166
14418
  },
14167
14419
  unionAll(other) {
14168
- text += ` UNION ALL ${String(other.toSQL())}`;
14169
- built = null;
14420
+ appendSetOp("UNION ALL", other);
14421
+ return this;
14422
+ },
14423
+ intersect(other) {
14424
+ appendSetOp("INTERSECT", other);
14425
+ return this;
14426
+ },
14427
+ intersectAll(other) {
14428
+ appendSetOp("INTERSECT ALL", other);
14429
+ return this;
14430
+ },
14431
+ except(other) {
14432
+ appendSetOp("EXCEPT", other);
14433
+ return this;
14434
+ },
14435
+ exceptAll(other) {
14436
+ appendSetOp("EXCEPT ALL", other);
14170
14437
  return this;
14171
14438
  },
14172
14439
  forPage(page, perPage) {
@@ -14210,11 +14477,22 @@ function createQueryBuilder(state) {
14210
14477
  const e = await this.exists();
14211
14478
  return !e;
14212
14479
  },
14213
- async paginate(perPage, page = 1) {
14480
+ async paginate(perPage, page = 1, opts = {}) {
14214
14481
  if (!Number.isFinite(perPage) || perPage <= 0 || !Number.isInteger(perPage))
14215
14482
  throw new TypeError(`[query-builder] paginate(perPage): expected positive integer, got ${perPage}`);
14216
14483
  if (!Number.isFinite(page) || page < 1 || !Number.isInteger(page))
14217
14484
  throw new TypeError(`[query-builder] paginate(page): expected integer >= 1, got ${page}`);
14485
+ if (opts.tx) {
14486
+ const baseSql = reorderSelectClauses(text);
14487
+ const baseParams = [...whereParams];
14488
+ const cRows2 = await opts.tx.unsafe(`SELECT COUNT(*) as c FROM (${baseSql}) as sub`, baseParams);
14489
+ const total2 = Number(cRows2?.[0]?.c ?? 0);
14490
+ const lastPage2 = Math.max(1, Math.ceil(total2 / perPage));
14491
+ const p2 = Math.max(1, Math.min(page, lastPage2));
14492
+ const offset2 = (p2 - 1) * perPage;
14493
+ const data2 = await opts.tx.unsafe(`${baseSql} LIMIT ${perPage} OFFSET ${offset2}`, baseParams);
14494
+ return { data: data2, meta: { perPage, page: p2, total: total2, lastPage: lastPage2 } };
14495
+ }
14218
14496
  const countQ = sql`SELECT COUNT(*) as c FROM (${ensureBuilt()}) as sub`;
14219
14497
  const cRows = await runWithHooks(countQ, "select", { signal: abortSignal, timeoutMs });
14220
14498
  const [cRow] = cRows;
@@ -14371,7 +14649,7 @@ function createQueryBuilder(state) {
14371
14649
  },
14372
14650
  async get() {
14373
14651
  const hooks = config5.hooks;
14374
- const hasQueryHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan);
14652
+ const hasQueryHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hasSlowQueryHook(hooks));
14375
14653
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !hasQueryHooks) {
14376
14654
  const prepareFn = _sql._prepareStatement;
14377
14655
  if (prepareFn) {
@@ -14426,7 +14704,7 @@ function createQueryBuilder(state) {
14426
14704
  },
14427
14705
  async first() {
14428
14706
  const fHooks = config5.hooks;
14429
- const fHasQueryHooks = fHooks && (fHooks.onQueryStart || fHooks.onQueryEnd || fHooks.onQueryError || fHooks.startSpan);
14707
+ const fHasQueryHooks = fHooks && (fHooks.onQueryStart || fHooks.onQueryEnd || fHooks.onQueryError || fHooks.startSpan || hasSlowQueryHook(fHooks));
14430
14708
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !fHasQueryHooks) {
14431
14709
  const prepareFn = _sql._prepareStatement;
14432
14710
  if (prepareFn) {
@@ -14508,7 +14786,7 @@ function createQueryBuilder(state) {
14508
14786
  countText = `SELECT COUNT(*) as c FROM ${table}`;
14509
14787
  }
14510
14788
  const cHooks = config5.hooks;
14511
- const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan);
14789
+ const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan || hasSlowQueryHook(cHooks));
14512
14790
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
14513
14791
  const prepareFn = _sql._prepareStatement;
14514
14792
  if (prepareFn) {
@@ -14526,7 +14804,7 @@ function createQueryBuilder(state) {
14526
14804
  const fromIdx = text.indexOf(" FROM ");
14527
14805
  const avgText = fromIdx !== -1 ? `SELECT AVG(${column}) as a${text.substring(fromIdx)}` : `SELECT AVG(${column}) as a FROM ${table}`;
14528
14806
  const aHooks = config5.hooks;
14529
- const aHasHooks = aHooks && (aHooks.onQueryStart || aHooks.onQueryEnd || aHooks.onQueryError || aHooks.startSpan);
14807
+ const aHasHooks = aHooks && (aHooks.onQueryStart || aHooks.onQueryEnd || aHooks.onQueryError || aHooks.startSpan || hasSlowQueryHook(aHooks));
14530
14808
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !aHasHooks) {
14531
14809
  const prepareFn = _sql._prepareStatement;
14532
14810
  if (prepareFn) {
@@ -14596,6 +14874,9 @@ function createQueryBuilder(state) {
14596
14874
  toParams() {
14597
14875
  return ensureBuilt().values?.() ?? [];
14598
14876
  },
14877
+ __rawState() {
14878
+ return { sql: reorderSelectClauses(text), params: [...whereParams] };
14879
+ },
14599
14880
  raw() {
14600
14881
  return ensureBuilt().raw();
14601
14882
  },
@@ -14954,15 +15235,15 @@ function createQueryBuilder(state) {
14954
15235
  params.length = totalParams;
14955
15236
  if (rowCount === 1) {
14956
15237
  if (!isPostgres) {
14957
- let cols = keys[0];
15238
+ let cols = quoteId(keys[0]);
14958
15239
  let placeholders = "?";
14959
15240
  params[0] = firstRow[keys[0]];
14960
15241
  for (let c = 1;c < colCount; c++) {
14961
- cols += `,${keys[c]}`;
15242
+ cols += `,${quoteId(keys[c])}`;
14962
15243
  placeholders += ",?";
14963
15244
  params[c] = firstRow[keys[c]];
14964
15245
  }
14965
- sqlText = `INSERT INTO ${table}(${cols})VALUES(${placeholders})`;
15246
+ sqlText = `INSERT INTO ${quoteId(table)}(${cols})VALUES(${placeholders})`;
14966
15247
  } else {
14967
15248
  const columnList = keys.map((k) => quoteId(k)).join(",");
14968
15249
  sqlText = `INSERT INTO ${quoteId(table)}(${columnList})VALUES(`;
@@ -15020,7 +15301,7 @@ function createQueryBuilder(state) {
15020
15301
  },
15021
15302
  execute() {
15022
15303
  const hooks = config5.hooks;
15023
- const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate);
15304
+ const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15024
15305
  if (!hasHooks) {
15025
15306
  const prepareFn = _sql._prepareStatement;
15026
15307
  if (prepareFn) {
@@ -15494,55 +15775,72 @@ function createQueryBuilder(state) {
15494
15775
  };
15495
15776
  },
15496
15777
  async insertOrIgnore(table, values) {
15497
- if (config5.dialect === "mysql") {
15498
- const built2 = bunSql`INSERT IGNORE INTO ${bunSql(String(table))} ${bunSql(values)}`;
15499
- return built2.execute();
15500
- }
15501
- const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} ON CONFLICT DO NOTHING`;
15502
- return built.execute();
15778
+ const rows = Array.isArray(values) ? values : [values];
15779
+ if (!rows.length)
15780
+ return;
15781
+ const { colsSql, valuesSql, params } = buildInsertClause(rows);
15782
+ const tbl = quoteInsertIdent(String(table));
15783
+ const sqlText = config5.dialect === "mysql" ? `INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}` : `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} ON CONFLICT DO NOTHING`;
15784
+ return bunSql.unsafe(sqlText, params).execute();
15503
15785
  },
15504
15786
  async insertGetId(table, values, idColumn = "id") {
15787
+ const { colsSql, valuesSql, params } = buildInsertClause([values]);
15788
+ const tbl = quoteInsertIdent(String(table));
15505
15789
  if (config5.dialect === "mysql") {
15506
- const insertQuery = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)}`;
15507
- const result = await insertQuery.execute();
15508
- if (result && typeof result === "object" && "insertId" in result) {
15509
- return result.insertId;
15510
- }
15511
- const [lastIdResult] = await bunSql`SELECT LAST_INSERT_ID() as id`.execute();
15512
- return lastIdResult?.id;
15513
- } else {
15514
- const q = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} RETURNING ${bunSql(String(idColumn))} as id`;
15515
- const [row] = await q.execute();
15516
- return row?.id;
15517
- }
15790
+ await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
15791
+ const [row2] = await bunSql.unsafe(`SELECT LAST_INSERT_ID() as id`).execute();
15792
+ return row2?.id;
15793
+ }
15794
+ if (config5.dialect === "sqlite") {
15795
+ const res = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
15796
+ if (res?.lastInsertRowid != null)
15797
+ return res.lastInsertRowid;
15798
+ const [row2] = await bunSql.unsafe(`SELECT last_insert_rowid() as id`).execute();
15799
+ return row2?.id;
15800
+ }
15801
+ const [row] = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} RETURNING ${quoteInsertIdent(String(idColumn))} as id`, params).execute();
15802
+ return row?.id;
15518
15803
  },
15519
15804
  async updateOrInsert(table, match, values) {
15520
- const whereParts = Object.keys(match).map((k) => bunSql`${bunSql(String(k))} = ${bunSql(match[k])}`);
15521
- const existsQ = bunSql`SELECT 1 FROM ${bunSql(String(table))} WHERE ${bunSql(whereParts)} LIMIT 1`;
15522
- const existsRows = await existsQ.execute();
15805
+ const tbl = quoteInsertIdent(String(table));
15806
+ const matchKeys = Object.keys(match);
15807
+ let idx = 1;
15808
+ const whereSql = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(idx++)}`).join(" AND ");
15809
+ const whereParams = matchKeys.map((k) => match[k]);
15810
+ const existsRows = await bunSql.unsafe(`SELECT 1 FROM ${tbl} WHERE ${whereSql} LIMIT 1`, whereParams).execute();
15523
15811
  if (existsRows.length) {
15524
- const upd = bunSql`UPDATE ${bunSql(String(table))} SET ${bunSql(values)} WHERE ${bunSql(whereParts)}`;
15525
- await upd.execute();
15526
- return true;
15527
- } else {
15528
- const ins = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql({ ...match, ...values })}`;
15529
- await ins.execute();
15812
+ const setKeys = Object.keys(values);
15813
+ let i = 1;
15814
+ const setSql = setKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(", ");
15815
+ const whereSql2 = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(" AND ");
15816
+ const params2 = [...setKeys.map((k) => values[k]), ...matchKeys.map((k) => match[k])];
15817
+ await bunSql.unsafe(`UPDATE ${tbl} SET ${setSql} WHERE ${whereSql2}`, params2).execute();
15530
15818
  return true;
15531
15819
  }
15820
+ const { colsSql, valuesSql, params } = buildInsertClause([{ ...match, ...values }]);
15821
+ await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
15822
+ return true;
15532
15823
  },
15533
15824
  async upsert(table, rows, conflictColumns, mergeColumns) {
15534
15825
  const targetCols = conflictColumns.map((c) => String(c));
15535
15826
  const setCols = (mergeColumns ?? []).map((c) => String(c));
15827
+ const list = rows;
15828
+ if (!list.length)
15829
+ return;
15830
+ const { colsSql, valuesSql, params } = buildInsertClause(list);
15831
+ const tbl = quoteInsertIdent(String(table));
15832
+ const insert = `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`;
15536
15833
  if (config5.dialect === "mysql") {
15537
- const updateList2 = setCols.map((c) => `\`${c.replace(/`/g, "``")}\` = VALUES(\`${c.replace(/`/g, "``")}\`)`).join(", ");
15538
- const built2 = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON DUPLICATE KEY UPDATE ${bunSql.unsafe(updateList2)}`;
15539
- return built2.execute();
15540
- }
15541
- const isPostgres = config5.dialect === "postgres";
15542
- const quoteCol = (column) => isPostgres ? `"${column.replace(/"/g, '""')}"` : `"${column.replace(/"/g, '""')}"`;
15543
- const updateList = setCols.map((column) => `${quoteCol(column)} = EXCLUDED.${quoteCol(column)}`).join(", ");
15544
- const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON CONFLICT (${bunSql(targetCols)}) DO UPDATE SET ${bunSql.unsafe(updateList)}`;
15545
- return built.execute();
15834
+ if (setCols.length === 0)
15835
+ return bunSql.unsafe(`INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
15836
+ const updateList2 = setCols.map((c) => `${quoteInsertIdent(c)} = VALUES(${quoteInsertIdent(c)})`).join(", ");
15837
+ return bunSql.unsafe(`${insert} ON DUPLICATE KEY UPDATE ${updateList2}`, params).execute();
15838
+ }
15839
+ const targets = targetCols.map(quoteInsertIdent).join(", ");
15840
+ if (setCols.length === 0)
15841
+ return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO NOTHING`, params).execute();
15842
+ const updateList = setCols.map((c) => `${quoteInsertIdent(c)} = EXCLUDED.${quoteInsertIdent(c)}`).join(", ");
15843
+ return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO UPDATE SET ${updateList}`, params).execute();
15546
15844
  },
15547
15845
  async save(table, values) {
15548
15846
  const pk = meta?.primaryKeys[String(table)] ?? "id";
@@ -15645,7 +15943,8 @@ function createQueryBuilder(state) {
15645
15943
  const colCount = keys.length;
15646
15944
  const rowCount = rows.length;
15647
15945
  const params = Array.from({ length: rowCount * colCount });
15648
- let sql = `INSERT INTO ${table}(${keys.join(",")})VALUES`;
15946
+ const quoteId = config5.dialect === "mysql" ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
15947
+ let sql = `INSERT INTO ${quoteId(String(table))}(${keys.map(quoteId).join(",")})VALUES`;
15649
15948
  let pidx = 0;
15650
15949
  for (let r = 0;r < rowCount; r++) {
15651
15950
  if (r > 0)
@@ -15952,7 +16251,7 @@ var init_cache = __esm(() => {
15952
16251
  });
15953
16252
 
15954
16253
  // src/actions/console.ts
15955
- import process21 from "process";
16254
+ import process19 from "process";
15956
16255
  import { createInterface } from "readline";
15957
16256
  async function startConsole() {
15958
16257
  console.log("-- Query Builder Interactive Console");
@@ -15961,8 +16260,8 @@ async function startConsole() {
15961
16260
  console.log();
15962
16261
  const qb = createQueryBuilder();
15963
16262
  const rl = createInterface({
15964
- input: process21.stdin,
15965
- output: process21.stdout,
16263
+ input: process19.stdin,
16264
+ output: process19.stdout,
15966
16265
  prompt: "qb> "
15967
16266
  });
15968
16267
  let multilineBuffer = "";
@@ -16013,7 +16312,7 @@ Tips:
16013
16312
  if (cmd === ".exit" || cmd === ".quit") {
16014
16313
  console.log("Goodbye!");
16015
16314
  rl.close();
16016
- process21.exit(0);
16315
+ process19.exit(0);
16017
16316
  } else if (cmd === ".help") {
16018
16317
  console.log(helpText);
16019
16318
  } else if (cmd === ".clear") {
@@ -16084,7 +16383,7 @@ Tips:
16084
16383
  rl.on("close", () => {
16085
16384
  console.log(`
16086
16385
  Goodbye!`);
16087
- process21.exit(0);
16386
+ process19.exit(0);
16088
16387
  });
16089
16388
  rl.prompt();
16090
16389
  }
@@ -16462,9 +16761,9 @@ var init_db_info = __esm(() => {
16462
16761
  });
16463
16762
 
16464
16763
  // src/actions/db-optimize.ts
16465
- import process22 from "process";
16764
+ import process21 from "process";
16466
16765
  async function dbOptimize(options = {}) {
16467
- const dialect = options.dialect || process22.env.DB_DIALECT || "postgres";
16766
+ const dialect = options.dialect || process21.env.DB_DIALECT || "postgres";
16468
16767
  const aggressive = options.aggressive || false;
16469
16768
  if (options.verbose) {
16470
16769
  console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
@@ -16504,7 +16803,7 @@ async function dbOptimize(options = {}) {
16504
16803
  await bunSql`ANALYZE TABLE ${bunSql(table)}`;
16505
16804
  }
16506
16805
  } else {
16507
- const dbName = process22.env.DB_NAME || "test";
16806
+ const dbName = process21.env.DB_NAME || "test";
16508
16807
  const tables = await bunSql`
16509
16808
  SELECT table_name
16510
16809
  FROM information_schema.tables
@@ -16556,9 +16855,9 @@ var init_db_optimize = __esm(() => {
16556
16855
  });
16557
16856
 
16558
16857
  // src/actions/db-wipe.ts
16559
- import process23 from "process";
16858
+ import process22 from "process";
16560
16859
  async function dbWipe(options = {}) {
16561
- const dialect = options.dialect || process23.env.DB_DIALECT || "postgres";
16860
+ const dialect = options.dialect || process22.env.DB_DIALECT || "postgres";
16562
16861
  if (options.verbose) {
16563
16862
  console.log(`Wiping all tables from ${dialect} database...`);
16564
16863
  }
@@ -16572,7 +16871,7 @@ async function dbWipe(options = {}) {
16572
16871
  `;
16573
16872
  tables = result.map((row) => row.tablename);
16574
16873
  } else if (dialect === "mysql") {
16575
- const dbName = process23.env.DB_NAME || "test";
16874
+ const dbName = process22.env.DB_NAME || "test";
16576
16875
  const result = await bunSql`
16577
16876
  SELECT table_name
16578
16877
  FROM information_schema.tables
@@ -16865,10 +17164,130 @@ var init_introspect = __esm(() => {
16865
17164
  init_src2();
16866
17165
  });
16867
17166
 
17167
+ // src/actions/introspect-db.ts
17168
+ function sqlTypeToAttr(sqlType) {
17169
+ const t = sqlType.toLowerCase();
17170
+ if (/^bool|boolean|tinyint\(1\)/.test(t))
17171
+ return "boolean";
17172
+ if (/int|serial|numeric|decimal|double|real|float|money/.test(t))
17173
+ return "number";
17174
+ if (/timestamp|datetime|date|time/.test(t))
17175
+ return "datetime";
17176
+ if (/json/.test(t))
17177
+ return "json";
17178
+ return "string";
17179
+ }
17180
+ function singularize(name) {
17181
+ if (/ies$/i.test(name))
17182
+ return name.replace(/ies$/i, "y");
17183
+ if (/ses$/i.test(name))
17184
+ return name.replace(/es$/i, "");
17185
+ if (/s$/i.test(name) && !/ss$/i.test(name))
17186
+ return name.replace(/s$/i, "");
17187
+ return name;
17188
+ }
17189
+ function pascalCase(name) {
17190
+ return name.replace(/[-_\s]+/g, " ").split(" ").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
17191
+ }
17192
+ function modelNameForTable(table) {
17193
+ return pascalCase(singularize(table));
17194
+ }
17195
+ async function listTables(qb, dialect) {
17196
+ if (dialect === "postgres") {
17197
+ const rows2 = await qb.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`);
17198
+ return rows2.map((r) => r.table_name);
17199
+ }
17200
+ if (dialect === "mysql") {
17201
+ const rows2 = await qb.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE' ORDER BY table_name`);
17202
+ return rows2.map((r) => r.table_name ?? r.TABLE_NAME);
17203
+ }
17204
+ const rows = await qb.unsafe(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
17205
+ return rows.map((r) => r.name);
17206
+ }
17207
+ async function readColumns(qb, dialect, table) {
17208
+ if (dialect === "sqlite") {
17209
+ const rows2 = await qb.unsafe(`PRAGMA table_info(${table})`);
17210
+ return rows2.map((r) => ({
17211
+ name: r.name,
17212
+ sqlType: String(r.type || ""),
17213
+ nullable: Number(r.notnull) === 0,
17214
+ isPrimaryKey: Number(r.pk) > 0
17215
+ }));
17216
+ }
17217
+ if (dialect === "postgres") {
17218
+ const rows2 = await qb.unsafe(`SELECT c.column_name, c.data_type, c.is_nullable,
17219
+ (SELECT COUNT(*) FROM information_schema.table_constraints tc
17220
+ JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
17221
+ WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
17222
+ FROM information_schema.columns c
17223
+ WHERE c.table_name = $1 AND c.table_schema = 'public'
17224
+ ORDER BY c.ordinal_position`, [table]);
17225
+ return rows2.map((r) => ({
17226
+ name: r.column_name,
17227
+ sqlType: String(r.data_type || ""),
17228
+ nullable: String(r.is_nullable).toUpperCase() === "YES",
17229
+ isPrimaryKey: Number(r.is_pk) > 0
17230
+ }));
17231
+ }
17232
+ const rows = await qb.unsafe(`SELECT column_name, data_type, is_nullable, column_key
17233
+ FROM information_schema.columns
17234
+ WHERE table_name = ? AND table_schema = DATABASE()
17235
+ ORDER BY ordinal_position`, [table]);
17236
+ return rows.map((r) => ({
17237
+ name: r.column_name ?? r.COLUMN_NAME,
17238
+ sqlType: String(r.data_type ?? r.DATA_TYPE ?? ""),
17239
+ nullable: String(r.is_nullable ?? r.IS_NULLABLE).toUpperCase() === "YES",
17240
+ isPrimaryKey: String(r.column_key ?? r.COLUMN_KEY).toUpperCase() === "PRI"
17241
+ }));
17242
+ }
17243
+ function generateModelSource(table, columns) {
17244
+ const modelName = modelNameForTable(table);
17245
+ const pk = columns.find((c) => c.isPrimaryKey)?.name ?? "id";
17246
+ const attrLines = columns.map((c) => {
17247
+ const parts = [`type: '${sqlTypeToAttr(c.sqlType)}'`];
17248
+ if (!c.nullable && !c.isPrimaryKey)
17249
+ parts.push("required: true");
17250
+ return ` ${c.name}: { ${parts.join(", ")} },`;
17251
+ }).join(`
17252
+ `);
17253
+ return `export const ${modelName} = defineModel({
17254
+ name: '${modelName}',
17255
+ table: '${table}',
17256
+ primaryKey: '${pk}',
17257
+ attributes: {
17258
+ ${attrLines}
17259
+ },
17260
+ })
17261
+ `;
17262
+ }
17263
+ async function introspectDatabase(opts = {}) {
17264
+ const dialect = config5.dialect || "postgres";
17265
+ const qb = createQueryBuilder();
17266
+ const tables = opts.tables?.length ? opts.tables : await listTables(qb, dialect);
17267
+ const out = [];
17268
+ for (const table of tables) {
17269
+ const columns = await readColumns(qb, dialect, table);
17270
+ if (!columns.length)
17271
+ continue;
17272
+ out.push({
17273
+ table,
17274
+ modelName: modelNameForTable(table),
17275
+ primaryKey: columns.find((c) => c.isPrimaryKey)?.name ?? "id",
17276
+ columns,
17277
+ source: generateModelSource(table, columns)
17278
+ });
17279
+ }
17280
+ return out;
17281
+ }
17282
+ var init_introspect_db = __esm(() => {
17283
+ init_config();
17284
+ init_src2();
17285
+ });
17286
+
16868
17287
  // src/actions/make-model.ts
16869
17288
  import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync10 } from "fs";
16870
17289
  import { dirname as dirname5, join as join7 } from "path";
16871
- import process24 from "process";
17290
+ import process23 from "process";
16872
17291
  function findWorkspaceRoot(startPath) {
16873
17292
  let currentPath = startPath;
16874
17293
  while (currentPath !== dirname5(currentPath)) {
@@ -16877,10 +17296,10 @@ function findWorkspaceRoot(startPath) {
16877
17296
  }
16878
17297
  currentPath = dirname5(currentPath);
16879
17298
  }
16880
- return process24.cwd();
17299
+ return process23.cwd();
16881
17300
  }
16882
17301
  async function makeModel(name, options = {}) {
16883
- const workspaceRoot = findWorkspaceRoot(process24.cwd());
17302
+ const workspaceRoot = findWorkspaceRoot(process23.cwd());
16884
17303
  const modelsDir = options.dir || join7(workspaceRoot, "app/Models");
16885
17304
  if (!existsSync14(modelsDir)) {
16886
17305
  mkdirSync5(modelsDir, { recursive: true });
@@ -17437,7 +17856,7 @@ __export(exports_migrate, {
17437
17856
  import { existsSync as existsSync16, mkdirSync as mkdirSync7, mkdtempSync, readdirSync as readdirSync6, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
17438
17857
  import { tmpdir } from "os";
17439
17858
  import { join as join9 } from "path";
17440
- import process25 from "process";
17859
+ import process24 from "process";
17441
17860
  function info(message) {
17442
17861
  if (config5.verbose)
17443
17862
  console.log(message);
@@ -17484,7 +17903,7 @@ function savePlanSnapshot(workspaceRoot, dialect, plan) {
17484
17903
  info(`-- Model snapshot saved to ${snapshotPath}`);
17485
17904
  }
17486
17905
  function getWorkspaceRoot() {
17487
- return process25.cwd();
17906
+ return process24.cwd();
17488
17907
  }
17489
17908
  function ensureSqlDirectory(workspaceRoot) {
17490
17909
  const sqlDir = getSqlDirectory(workspaceRoot);
@@ -17496,7 +17915,7 @@ function ensureSqlDirectory(workspaceRoot) {
17496
17915
  }
17497
17916
  async function generateMigration(dir, opts = {}) {
17498
17917
  if (!dir) {
17499
- dir = join9(process25.cwd(), "app/Models");
17918
+ dir = join9(process24.cwd(), "app/Models");
17500
17919
  }
17501
17920
  const dialect = opts.dialect || config5.dialect || "postgres";
17502
17921
  const workspaceRoot = getWorkspaceRoot();
@@ -17551,7 +17970,7 @@ async function generateMigration(dir, opts = {}) {
17551
17970
  }
17552
17971
  async function executeMigration(dir) {
17553
17972
  if (!dir) {
17554
- dir = join9(process25.cwd(), "app/Models");
17973
+ dir = join9(process24.cwd(), "app/Models");
17555
17974
  }
17556
17975
  const workspaceRoot = getWorkspaceRoot();
17557
17976
  const sqlDir = ensureSqlDirectory(workspaceRoot);
@@ -17616,7 +18035,7 @@ async function executeMigration(dir) {
17616
18035
  }
17617
18036
  async function resetDatabase(dir, opts = {}) {
17618
18037
  if (!dir) {
17619
- dir = join9(process25.cwd(), "app/Models");
18038
+ dir = join9(process24.cwd(), "app/Models");
17620
18039
  }
17621
18040
  const dialect = opts.dialect || "postgres";
17622
18041
  const driver = getDialectDriver(dialect);
@@ -17709,7 +18128,7 @@ async function resetDatabase(dir, opts = {}) {
17709
18128
  }
17710
18129
  async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
17711
18130
  if (!dir) {
17712
- dir = join9(process25.cwd(), "app/Models");
18131
+ dir = join9(process24.cwd(), "app/Models");
17713
18132
  }
17714
18133
  if (!workspaceRoot) {
17715
18134
  workspaceRoot = getWorkspaceRoot();
@@ -17807,9 +18226,51 @@ var init_migrate_generate = __esm(() => {
17807
18226
  });
17808
18227
 
17809
18228
  // src/actions/migrate-rollback.ts
17810
- import { existsSync as existsSync17, unlinkSync as unlinkSync2 } from "fs";
18229
+ import { existsSync as existsSync17, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
17811
18230
  import { dirname as dirname7, join as join10 } from "path";
17812
- import process26 from "process";
18231
+ import process25 from "process";
18232
+ function splitSqlStatements2(sql) {
18233
+ const out = [];
18234
+ let buf = "";
18235
+ let inString = false;
18236
+ const lines = sql.split(`
18237
+ `).filter((l) => !/^\s*--/.test(l));
18238
+ const text = lines.join(`
18239
+ `);
18240
+ for (let i = 0;i < text.length; i++) {
18241
+ const ch = text[i];
18242
+ if (ch === "'")
18243
+ inString = !inString;
18244
+ if (ch === ";" && !inString) {
18245
+ if (buf.trim())
18246
+ out.push(buf.trim());
18247
+ buf = "";
18248
+ } else {
18249
+ buf += ch;
18250
+ }
18251
+ }
18252
+ if (buf.trim())
18253
+ out.push(buf.trim());
18254
+ return out;
18255
+ }
18256
+ function deriveDownStatements(forwardSql, dialect = config5.dialect) {
18257
+ const q = (id) => dialect === "mysql" ? `\`${id}\`` : `"${id}"`;
18258
+ const down = [];
18259
+ const skipped = [];
18260
+ for (const stmt of splitSqlStatements2(forwardSql)) {
18261
+ let m;
18262
+ if (m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
18263
+ down.push(`DROP TABLE IF EXISTS ${q(m[1])}`);
18264
+ } else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
18265
+ down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(m[2])}`);
18266
+ } else if (m = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?(?:\s+ON\s+["`']?(\w+)["`']?)?/i.exec(stmt)) {
18267
+ down.push(dialect === "mysql" && m[2] ? `DROP INDEX ${q(m[1])} ON ${q(m[2])}` : `DROP INDEX IF EXISTS ${q(m[1])}`);
18268
+ } else {
18269
+ skipped.push(stmt);
18270
+ }
18271
+ }
18272
+ return { down: down.reverse(), skipped };
18273
+ }
17813
18274
  function findWorkspaceRoot2(startPath) {
17814
18275
  let currentPath = startPath;
17815
18276
  while (currentPath !== dirname7(currentPath)) {
@@ -17818,16 +18279,17 @@ function findWorkspaceRoot2(startPath) {
17818
18279
  }
17819
18280
  currentPath = dirname7(currentPath);
17820
18281
  }
17821
- return process26.cwd();
18282
+ return process25.cwd();
17822
18283
  }
17823
18284
  function getSqlDirectory2(workspaceRoot) {
17824
18285
  if (!workspaceRoot) {
17825
- workspaceRoot = findWorkspaceRoot2(process26.cwd());
18286
+ workspaceRoot = findWorkspaceRoot2(process25.cwd());
17826
18287
  }
17827
18288
  return join10(workspaceRoot, "database", "migrations");
17828
18289
  }
17829
18290
  async function migrateRollback(options = {}) {
17830
18291
  const steps = options.steps || 1;
18292
+ const reverseSchema = options.reverseSchema !== false;
17831
18293
  console.log("-- Rolling back migrations");
17832
18294
  console.log(`-- Steps: ${steps}`);
17833
18295
  console.log();
@@ -17857,13 +18319,25 @@ async function migrateRollback(options = {}) {
17857
18319
  console.log(` - ${migration.migration}`);
17858
18320
  }
17859
18321
  console.log();
18322
+ const sqlDir = getSqlDirectory2();
18323
+ let reversedAny = false;
17860
18324
  for (const migration of migrationsToRollback) {
17861
18325
  try {
18326
+ const filePath = join10(sqlDir, migration.migration);
18327
+ if (reverseSchema && existsSync17(filePath)) {
18328
+ const forwardSql = readFileSync4(filePath, "utf8");
18329
+ const { down, skipped } = deriveDownStatements(forwardSql);
18330
+ for (const stmt of down) {
18331
+ await qb.unsafe(stmt);
18332
+ console.log(`-- \u21A9 ${stmt}`);
18333
+ reversedAny = true;
18334
+ }
18335
+ if (skipped.length > 0)
18336
+ console.log(`-- \u26A0\uFE0F ${skipped.length} statement(s) in ${migration.migration} could not be auto-reversed (data/complex DDL) \u2014 reverse manually.`);
18337
+ }
17862
18338
  const deleteSql = `DELETE FROM migrations WHERE migration = $1`;
17863
18339
  await qb.unsafe(deleteSql, [migration.migration]);
17864
18340
  console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
17865
- const sqlDir = getSqlDirectory2();
17866
- const filePath = join10(sqlDir, migration.migration);
17867
18341
  if (existsSync17(filePath)) {
17868
18342
  unlinkSync2(filePath);
17869
18343
  console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
@@ -17874,24 +18348,25 @@ async function migrateRollback(options = {}) {
17874
18348
  }
17875
18349
  }
17876
18350
  console.log();
17877
- console.log("-- \u26A0\uFE0F Important: Rollback only removes migration records.");
17878
- console.log("-- To reverse schema changes:");
17879
- console.log("-- 1. Revert your model changes");
17880
- console.log("-- 2. Run `qb migrate:fresh` to rebuild the database");
17881
- console.log("-- or manually write and execute reverse SQL");
18351
+ if (reverseSchema) {
18352
+ console.log(reversedAny ? "-- \u2713 Reverse DDL executed for the rolled-back migration(s)." : "-- \u26A0\uFE0F No reversible DDL found; only migration records were removed.");
18353
+ } else {
18354
+ console.log("-- \u26A0\uFE0F reverseSchema disabled: only migration records were removed.");
18355
+ }
17882
18356
  } catch (err) {
17883
18357
  console.error("-- Rollback failed:", err);
17884
18358
  throw err;
17885
18359
  }
17886
18360
  }
17887
18361
  var init_migrate_rollback = __esm(() => {
18362
+ init_config();
17888
18363
  init_src2();
17889
18364
  });
17890
18365
 
17891
18366
  // src/actions/migrate-status.ts
17892
18367
  import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
17893
18368
  import { dirname as dirname8, join as join11 } from "path";
17894
- import process27 from "process";
18369
+ import process26 from "process";
17895
18370
  function findWorkspaceRoot3(startPath) {
17896
18371
  let currentPath = startPath;
17897
18372
  while (currentPath !== dirname8(currentPath)) {
@@ -17900,11 +18375,11 @@ function findWorkspaceRoot3(startPath) {
17900
18375
  }
17901
18376
  currentPath = dirname8(currentPath);
17902
18377
  }
17903
- return process27.cwd();
18378
+ return process26.cwd();
17904
18379
  }
17905
18380
  function getSqlDirectory3(workspaceRoot) {
17906
18381
  if (!workspaceRoot) {
17907
- workspaceRoot = findWorkspaceRoot3(process27.cwd());
18382
+ workspaceRoot = findWorkspaceRoot3(process26.cwd());
17908
18383
  }
17909
18384
  return join11(workspaceRoot, "database", "migrations");
17910
18385
  }
@@ -17998,9 +18473,9 @@ var init_migrate_status = __esm(() => {
17998
18473
  // src/actions/model-show.ts
17999
18474
  import { readdirSync as readdirSync9 } from "fs";
18000
18475
  import { extname as extname2, join as join12 } from "path";
18001
- import process28 from "process";
18476
+ import process27 from "process";
18002
18477
  async function modelShow(modelName, options = {}) {
18003
- const dir = options.dir || join12(process28.cwd(), "app/Models");
18478
+ const dir = options.dir || join12(process27.cwd(), "app/Models");
18004
18479
  try {
18005
18480
  const files = readdirSync9(dir);
18006
18481
  const modelFile = files.find((f) => {
@@ -18119,7 +18594,7 @@ var init_ping = __esm(() => {
18119
18594
  });
18120
18595
 
18121
18596
  // src/actions/query-explain-all.ts
18122
- import { readdirSync as readdirSync10, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
18597
+ import { readdirSync as readdirSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
18123
18598
  import { extname as extname3, join as join13 } from "path";
18124
18599
  async function queryExplainAll(path, options = {}) {
18125
18600
  const results = [];
@@ -18145,7 +18620,7 @@ async function queryExplainAll(path, options = {}) {
18145
18620
  }
18146
18621
  for (const file2 of files) {
18147
18622
  try {
18148
- const query = readFileSync4(file2, "utf8").trim();
18623
+ const query = readFileSync5(file2, "utf8").trim();
18149
18624
  if (!query) {
18150
18625
  if (options.verbose) {
18151
18626
  console.log(`\u2298 ${file2}: empty file`);
@@ -18182,7 +18657,7 @@ async function queryExplainAll(path, options = {}) {
18182
18657
  } catch (error) {
18183
18658
  results.push({
18184
18659
  file: file2,
18185
- query: readFileSync4(file2, "utf8").trim(),
18660
+ query: readFileSync5(file2, "utf8").trim(),
18186
18661
  plan: [],
18187
18662
  error: error.message
18188
18663
  });
@@ -18214,9 +18689,9 @@ var init_query_explain_all = __esm(() => {
18214
18689
  // src/actions/relation-diagram.ts
18215
18690
  import { writeFileSync as writeFileSync12 } from "fs";
18216
18691
  import { join as join14 } from "path";
18217
- import process29 from "process";
18692
+ import process28 from "process";
18218
18693
  async function relationDiagram(options = {}) {
18219
- const dir = options.dir || join14(process29.cwd(), "app/Models");
18694
+ const dir = options.dir || join14(process28.cwd(), "app/Models");
18220
18695
  const format = options.format || "mermaid";
18221
18696
  try {
18222
18697
  const models = await loadModels({ modelsDir: dir });
@@ -18338,7 +18813,7 @@ var init_relation_diagram = __esm(() => {
18338
18813
  // src/actions/seed.ts
18339
18814
  import { existsSync as existsSync19, mkdirSync as mkdirSync8, readdirSync as readdirSync11, writeFileSync as writeFileSync13 } from "fs";
18340
18815
  import { dirname as dirname9, join as join15 } from "path";
18341
- import process30 from "process";
18816
+ import process29 from "process";
18342
18817
  function findWorkspaceRoot4(startPath) {
18343
18818
  let currentPath = startPath;
18344
18819
  while (currentPath !== dirname9(currentPath)) {
@@ -18347,7 +18822,7 @@ function findWorkspaceRoot4(startPath) {
18347
18822
  }
18348
18823
  currentPath = dirname9(currentPath);
18349
18824
  }
18350
- return process30.cwd();
18825
+ return process29.cwd();
18351
18826
  }
18352
18827
  async function loadSeeders(seedersDir) {
18353
18828
  if (!existsSync19(seedersDir)) {
@@ -18379,7 +18854,7 @@ async function loadSeeders(seedersDir) {
18379
18854
  return seeders;
18380
18855
  }
18381
18856
  async function runSeeders(config6 = {}) {
18382
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
18857
+ const workspaceRoot = findWorkspaceRoot4(process29.cwd());
18383
18858
  const seedersDir = config6.seedersDir || join15(workspaceRoot, "database/seeders");
18384
18859
  const verbose = config6.verbose ?? true;
18385
18860
  if (verbose) {
@@ -18414,7 +18889,7 @@ async function runSeeders(config6 = {}) {
18414
18889
  }
18415
18890
  }
18416
18891
  async function runSeeder(className, options = {}) {
18417
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
18892
+ const workspaceRoot = findWorkspaceRoot4(process29.cwd());
18418
18893
  const seedersDir = join15(workspaceRoot, "database/seeders");
18419
18894
  const verbose = options.verbose ?? true;
18420
18895
  if (verbose) {
@@ -18438,7 +18913,7 @@ async function runSeeder(className, options = {}) {
18438
18913
  }
18439
18914
  }
18440
18915
  async function makeSeeder(name) {
18441
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
18916
+ const workspaceRoot = findWorkspaceRoot4(process29.cwd());
18442
18917
  const seedersDir = join15(workspaceRoot, "database/seeders");
18443
18918
  if (!existsSync19(seedersDir)) {
18444
18919
  mkdirSync8(seedersDir, { recursive: true });
@@ -18502,7 +18977,7 @@ export default class ${className} extends Seeder {
18502
18977
  console.log(`-- \u2713 Created seeder: ${filePath}`);
18503
18978
  }
18504
18979
  async function freshDatabase(options = {}) {
18505
- const workspaceRoot = findWorkspaceRoot4(process30.cwd());
18980
+ const workspaceRoot = findWorkspaceRoot4(process29.cwd());
18506
18981
  const modelsDir = options.modelsDir || join15(workspaceRoot, "app/Models");
18507
18982
  const seedersDir = options.seedersDir || join15(workspaceRoot, "database/seeders");
18508
18983
  const verbose = options.verbose ?? true;
@@ -18564,7 +19039,7 @@ var init_unsafe = __esm(() => {
18564
19039
  // src/actions/validate.ts
18565
19040
  import { existsSync as existsSync20 } from "fs";
18566
19041
  import { dirname as dirname10, join as join16 } from "path";
18567
- import process31 from "process";
19042
+ import process30 from "process";
18568
19043
  function findWorkspaceRoot5(startPath) {
18569
19044
  let currentPath = startPath;
18570
19045
  while (currentPath !== dirname10(currentPath)) {
@@ -18573,11 +19048,11 @@ function findWorkspaceRoot5(startPath) {
18573
19048
  }
18574
19049
  currentPath = dirname10(currentPath);
18575
19050
  }
18576
- return process31.cwd();
19051
+ return process30.cwd();
18577
19052
  }
18578
19053
  async function validateSchema(dir) {
18579
19054
  if (!dir) {
18580
- dir = join16(findWorkspaceRoot5(process31.cwd()), "app/Models");
19055
+ dir = join16(findWorkspaceRoot5(process30.cwd()), "app/Models");
18581
19056
  }
18582
19057
  const dialect = config5.dialect || "postgres";
18583
19058
  console.log("-- Validating Schema");
@@ -18796,6 +19271,7 @@ var init_actions = __esm(() => {
18796
19271
  init_file();
18797
19272
  init_inspect();
18798
19273
  init_introspect();
19274
+ init_introspect_db();
18799
19275
  init_make_model();
18800
19276
  init_migrate();
18801
19277
  init_migrate_generate();
@@ -23415,6 +23891,14 @@ function getDatabase() {
23415
23891
  return exec.sqliteDb;
23416
23892
  throw new Error(`[bun-query-builder] getDatabase() is only available for the sqlite dialect; ` + `the configured dialect is '${exec.dialect}'. Use the async model API instead.`);
23417
23893
  }
23894
+ function softDeletesEnabled(definition) {
23895
+ const t2 = definition.traits;
23896
+ return Boolean(t2?.useSoftDeletes || t2?.softDeletable);
23897
+ }
23898
+ function timestampsEnabled(definition) {
23899
+ const t2 = definition.traits;
23900
+ return Boolean(t2?.useTimestamps || t2?.timestampable);
23901
+ }
23418
23902
  function collectBelongsToManyKeys(definition) {
23419
23903
  const keys = new Set;
23420
23904
  const rel = definition.belongsToMany;
@@ -23584,7 +24068,7 @@ class ModelInstance {
23584
24068
  if (changeKeys.length > 0) {
23585
24069
  const sets = changeKeys.map((k2) => `${k2} = ?`).join(", ");
23586
24070
  const values = [...Object.values(changes), this._attributes[pk]];
23587
- if (this._definition.traits?.useTimestamps) {
24071
+ if (timestampsEnabled(this._definition)) {
23588
24072
  const now = formatNow();
23589
24073
  await exec.run(`UPDATE ${this._definition.table} SET ${sets}, updated_at = ? WHERE ${pk} = ?`, [...Object.values(changes), now, this._attributes[pk]]);
23590
24074
  } else {
@@ -23596,11 +24080,13 @@ class ModelInstance {
23596
24080
  const attrs = this._definition.attributes;
23597
24081
  const data = {};
23598
24082
  for (const [key, attr] of Object.entries(attrs)) {
23599
- if (attr.fillable && this._attributes[key] !== undefined) {
24083
+ if (attr.guarded)
24084
+ continue;
24085
+ if (this._attributes[key] !== undefined) {
23600
24086
  data[key] = this._attributes[key];
23601
24087
  }
23602
24088
  }
23603
- if (this._definition.traits?.useTimestamps) {
24089
+ if (timestampsEnabled(this._definition)) {
23604
24090
  const now = formatNow();
23605
24091
  data.created_at = now;
23606
24092
  data.updated_at = now;
@@ -23646,14 +24132,31 @@ class ModelInstance {
23646
24132
  if (!pkValue)
23647
24133
  throw new Error("Cannot delete a model without a primary key");
23648
24134
  await hooks?.beforeDelete?.(this);
23649
- if (this._definition.traits?.useSoftDeletes) {
23650
- await exec.run(`UPDATE ${this._definition.table} SET deleted_at = ? WHERE ${pk} = ?`, [formatNow(), pkValue]);
24135
+ if (softDeletesEnabled(this._definition)) {
24136
+ const now = formatNow();
24137
+ await exec.run(`UPDATE ${this._definition.table} SET deleted_at = ? WHERE ${pk} = ?`, [now, pkValue]);
24138
+ this._attributes[SOFT_DELETE_COLUMN] = now;
23651
24139
  } else {
23652
24140
  await exec.run(`DELETE FROM ${this._definition.table} WHERE ${pk} = ?`, [pkValue]);
23653
24141
  }
23654
24142
  await hooks?.afterDelete?.(this);
23655
24143
  return true;
23656
24144
  }
24145
+ async restore() {
24146
+ if (!softDeletesEnabled(this._definition))
24147
+ throw new Error(`[orm] restore() requires soft deletes on '${this._definition.name}'`);
24148
+ const pk = this._definition.primaryKey || "id";
24149
+ const pkValue = this._attributes[pk];
24150
+ if (!pkValue)
24151
+ throw new Error("Cannot restore a model without a primary key");
24152
+ await getExecutor().run(`UPDATE ${this._definition.table} SET ${SOFT_DELETE_COLUMN} = ? WHERE ${pk} = ?`, [null, pkValue]);
24153
+ this._attributes[SOFT_DELETE_COLUMN] = null;
24154
+ this._original = null;
24155
+ return this;
24156
+ }
24157
+ trashed() {
24158
+ return this._attributes[SOFT_DELETE_COLUMN] != null;
24159
+ }
23657
24160
  async refresh() {
23658
24161
  const exec = getExecutor();
23659
24162
  const pk = this._definition.primaryKey || "id";
@@ -23958,9 +24461,23 @@ class BelongsToManyRelationBuilder {
23958
24461
  this._offset = n2;
23959
24462
  return this;
23960
24463
  }
24464
+ relatedSelectColumns() {
24465
+ const cols = new Set([this.relatedPk, ...Object.keys(this._relatedDef.attributes ?? {})]);
24466
+ const t2 = this._relatedDef.traits;
24467
+ if (t2?.useTimestamps || t2?.timestampable) {
24468
+ cols.add("created_at");
24469
+ cols.add("updated_at");
24470
+ }
24471
+ if (t2?.useSoftDeletes || t2?.softDeletable)
24472
+ cols.add("deleted_at");
24473
+ if (t2?.useUuid)
24474
+ cols.add("uuid");
24475
+ return [...cols];
24476
+ }
23961
24477
  buildSelect() {
23962
24478
  const params = [];
23963
- let sql2 = `SELECT ${this.relatedTable}.*, ${this.pivotTable}.* FROM ${this.relatedTable}`;
24479
+ const relatedSelect = this.relatedSelectColumns().map((c2) => `${this.relatedTable}.${c2} AS ${BTM_RELATED_ALIAS}${c2}`).join(", ");
24480
+ let sql2 = `SELECT ${relatedSelect}, ${this.pivotTable}.* FROM ${this.relatedTable}`;
23964
24481
  sql2 += ` INNER JOIN ${this.pivotTable} ON ${this.pivotTable}.${this.fkRelated} = ${this.relatedTable}.${this.relatedPk}`;
23965
24482
  sql2 += ` WHERE ${this.pivotTable}.${this.fkParent} = ?`;
23966
24483
  params.push(this.parentId);
@@ -23981,25 +24498,17 @@ class BelongsToManyRelationBuilder {
23981
24498
  return { sql: sql2, params };
23982
24499
  }
23983
24500
  hydrateRows(rows) {
23984
- const relatedAttrs = new Set(Object.keys(this._relatedDef.attributes ?? {}));
23985
- relatedAttrs.add(this.relatedPk);
23986
- relatedAttrs.add("created_at");
23987
- relatedAttrs.add("updated_at");
23988
- relatedAttrs.add("deleted_at");
23989
24501
  const fkParent = this.fkParent;
23990
24502
  const fkRelated = this.fkRelated;
23991
24503
  return rows.map((raw) => {
23992
24504
  const relatedRow = {};
23993
24505
  const pivotExtras = {};
23994
24506
  for (const [k2, v2] of Object.entries(raw)) {
23995
- if (k2 === fkParent || k2 === fkRelated)
23996
- continue;
23997
- if (relatedAttrs.has(k2))
23998
- relatedRow[k2] = v2;
23999
- else
24507
+ if (k2.startsWith(BTM_RELATED_ALIAS))
24508
+ relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
24509
+ else if (k2 !== fkParent && k2 !== fkRelated)
24000
24510
  pivotExtras[k2] = v2;
24001
24511
  }
24002
- relatedRow[this.relatedPk] = raw[this.relatedPk];
24003
24512
  const inst = new ModelInstance(this._relatedDef, relatedRow);
24004
24513
  inst.pivot = pivotExtras;
24005
24514
  return inst;
@@ -24146,9 +24655,18 @@ class ModelQueryBuilder {
24146
24655
  _offset;
24147
24656
  _select = ["*"];
24148
24657
  _withRelations = [];
24658
+ _trashed = "exclude";
24149
24659
  constructor(definition) {
24150
24660
  this._definition = definition;
24151
24661
  }
24662
+ withTrashed() {
24663
+ this._trashed = "include";
24664
+ return this;
24665
+ }
24666
+ onlyTrashed() {
24667
+ this._trashed = "only";
24668
+ return this;
24669
+ }
24152
24670
  where(column, operatorOrValue, value) {
24153
24671
  if (value === undefined) {
24154
24672
  this._wheres.push({ column, operator: "=", value: operatorOrValue, boolean: "and" });
@@ -24326,11 +24844,24 @@ class ModelQueryBuilder {
24326
24844
  }
24327
24845
  return clauses.join(" ");
24328
24846
  }
24847
+ softDeleteClause() {
24848
+ if (this._trashed === "include" || !softDeletesEnabled(this._definition))
24849
+ return "";
24850
+ return this._trashed === "only" ? `${SOFT_DELETE_COLUMN} IS NOT NULL` : `${SOFT_DELETE_COLUMN} IS NULL`;
24851
+ }
24852
+ composeWhere(params) {
24853
+ const userClause = this._wheres.length > 0 ? this.buildWhereClauses(params) : "";
24854
+ const sd = this.softDeleteClause();
24855
+ if (userClause && sd)
24856
+ return `(${userClause}) AND ${sd}`;
24857
+ return userClause || sd;
24858
+ }
24329
24859
  buildQuery() {
24330
24860
  const params = [];
24331
24861
  let sql2 = `SELECT ${this._select.join(", ")} FROM ${this._definition.table}`;
24332
- if (this._wheres.length > 0) {
24333
- sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
24862
+ const whereBody = this.composeWhere(params);
24863
+ if (whereBody) {
24864
+ sql2 += ` WHERE ${whereBody}`;
24334
24865
  }
24335
24866
  if (this._orderBy.length > 0) {
24336
24867
  sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
@@ -24492,8 +25023,9 @@ class ModelQueryBuilder {
24492
25023
  const exec = getExecutor();
24493
25024
  const params = [];
24494
25025
  let sql2 = `SELECT COUNT(*) as count FROM ${this._definition.table}`;
24495
- if (this._wheres.length > 0) {
24496
- sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
25026
+ const whereBody = this.composeWhere(params);
25027
+ if (whereBody) {
25028
+ sql2 += ` WHERE ${whereBody}`;
24497
25029
  }
24498
25030
  const row = await exec.get(sql2, params);
24499
25031
  return Number(row?.count ?? 0);
@@ -24518,7 +25050,7 @@ class ModelQueryBuilder {
24518
25050
  const exec = getExecutor();
24519
25051
  const params = [amount];
24520
25052
  let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
24521
- if (this._definition.traits?.useTimestamps) {
25053
+ if (timestampsEnabled(this._definition)) {
24522
25054
  sql2 += `, updated_at = ?`;
24523
25055
  params.push(formatNow());
24524
25056
  }
@@ -24539,6 +25071,7 @@ class ModelQueryBuilder {
24539
25071
  builder._orderBy = [...this._orderBy];
24540
25072
  builder._select = [...this._select];
24541
25073
  builder._withRelations = [...this._withRelations];
25074
+ builder._trashed = this._trashed;
24542
25075
  builder._limit = size;
24543
25076
  builder._offset = page * size;
24544
25077
  const results = await builder.get();
@@ -24575,8 +25108,9 @@ class ModelQueryBuilder {
24575
25108
  const exec = getExecutor();
24576
25109
  const params = [];
24577
25110
  let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
24578
- if (this._wheres.length > 0) {
24579
- sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
25111
+ const whereBody = this.composeWhere(params);
25112
+ if (whereBody) {
25113
+ sql2 += ` WHERE ${whereBody}`;
24580
25114
  }
24581
25115
  if (this._orderBy.length > 0) {
24582
25116
  sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
@@ -24593,8 +25127,9 @@ class ModelQueryBuilder {
24593
25127
  const exec = getExecutor();
24594
25128
  const params = [];
24595
25129
  let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
24596
- if (this._wheres.length > 0) {
24597
- sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
25130
+ const whereBody = this.composeWhere(params);
25131
+ if (whereBody) {
25132
+ sql2 += ` WHERE ${whereBody}`;
24598
25133
  }
24599
25134
  const row = await exec.get(sql2, params);
24600
25135
  return row?.v == null ? null : Number(row.v);
@@ -24625,10 +25160,10 @@ class ModelQueryBuilder {
24625
25160
  const entries = Object.entries(data);
24626
25161
  const sets = entries.map(([k2]) => `${k2} = ?`).join(", ");
24627
25162
  const params = entries.map(([, v2]) => v2);
24628
- if (this._definition.traits?.useTimestamps) {
25163
+ if (timestampsEnabled(this._definition)) {
24629
25164
  params.push(formatNow());
24630
25165
  }
24631
- let sql2 = `UPDATE ${this._definition.table} SET ${sets}${this._definition.traits?.useTimestamps ? ", updated_at = ?" : ""}`;
25166
+ let sql2 = `UPDATE ${this._definition.table} SET ${sets}${timestampsEnabled(this._definition) ? ", updated_at = ?" : ""}`;
24632
25167
  if (this._wheres.length > 0) {
24633
25168
  sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
24634
25169
  }
@@ -24674,10 +25209,13 @@ function createModel(definition) {
24674
25209
  limit: (count) => new ModelQueryBuilder(definition).limit(count),
24675
25210
  take: (count) => new ModelQueryBuilder(definition).take(count),
24676
25211
  skip: (count) => new ModelQueryBuilder(definition).skip(count),
25212
+ withTrashed: () => new ModelQueryBuilder(definition).withTrashed(),
25213
+ onlyTrashed: () => new ModelQueryBuilder(definition).onlyTrashed(),
24677
25214
  async find(id) {
24678
25215
  const exec = getExecutor();
24679
25216
  const pk = definition.primaryKey || "id";
24680
- const row = await exec.get(`SELECT * FROM ${definition.table} WHERE ${pk} = ?`, [id]);
25217
+ const sd = softDeletesEnabled(definition) ? ` AND ${SOFT_DELETE_COLUMN} IS NULL` : "";
25218
+ const row = await exec.get(`SELECT * FROM ${definition.table} WHERE ${pk} = ?${sd}`, [id]);
24681
25219
  return row ? new ModelInstance(definition, row) : undefined;
24682
25220
  },
24683
25221
  async findOrFail(id) {
@@ -24689,7 +25227,8 @@ function createModel(definition) {
24689
25227
  async findMany(ids) {
24690
25228
  const exec = getExecutor();
24691
25229
  const pk = definition.primaryKey || "id";
24692
- const rows = await exec.all(`SELECT * FROM ${definition.table} WHERE ${pk} IN (${ids.map(() => "?").join(", ")})`, ids);
25230
+ const sd = softDeletesEnabled(definition) ? ` AND ${SOFT_DELETE_COLUMN} IS NULL` : "";
25231
+ const rows = await exec.all(`SELECT * FROM ${definition.table} WHERE ${pk} IN (${ids.map(() => "?").join(", ")})${sd}`, ids);
24693
25232
  return rows.map((row) => new ModelInstance(definition, row));
24694
25233
  },
24695
25234
  all: () => new ModelQueryBuilder(definition).get(),
@@ -24804,10 +25343,10 @@ async function createTableFromModel(definition) {
24804
25343
  }
24805
25344
  columns.push(colDef);
24806
25345
  }
24807
- if (definition.traits?.useTimestamps) {
25346
+ if (timestampsEnabled(definition)) {
24808
25347
  columns.push("created_at TEXT", "updated_at TEXT");
24809
25348
  }
24810
- if (definition.traits?.useSoftDeletes) {
25349
+ if (softDeletesEnabled(definition)) {
24811
25350
  columns.push("deleted_at TEXT");
24812
25351
  }
24813
25352
  await exec.run(`CREATE TABLE IF NOT EXISTS ${definition.table} (${columns.join(", ")})`, []);
@@ -24852,7 +25391,7 @@ async function seedModel(definition, count, faker) {
24852
25391
  if (attr.factory)
24853
25392
  data[name] = attr.factory(faker);
24854
25393
  }
24855
- if (definition.traits?.useTimestamps) {
25394
+ if (timestampsEnabled(definition)) {
24856
25395
  const now = formatNow();
24857
25396
  data.created_at = now;
24858
25397
  data.updated_at = now;
@@ -24863,7 +25402,7 @@ async function seedModel(definition, count, faker) {
24863
25402
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
24864
25403
  }
24865
25404
  }
24866
- var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, snakeCaseCache, tableNameCache, relationCache;
25405
+ var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", snakeCaseCache, tableNameCache, relationCache;
24867
25406
  var init_orm = __esm(() => {
24868
25407
  init_config();
24869
25408
  init_db();
@@ -26319,9 +26858,9 @@ function buildDatabaseSchema(models) {
26319
26858
  // src/loader.ts
26320
26859
  import { readdirSync as readdirSync12, statSync as statSync5 } from "fs";
26321
26860
  import { basename, extname as extname4 } from "path";
26322
- import process33 from "process";
26861
+ import process31 from "process";
26323
26862
  async function loadModels(options) {
26324
- const cwd = options.cwd ?? process33.cwd();
26863
+ const cwd = options.cwd ?? process31.cwd();
26325
26864
  const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
26326
26865
  const result = {};
26327
26866
  const entries = readdirSync12(dir);
@@ -26348,6 +26887,23 @@ async function loadModels(options) {
26348
26887
  }
26349
26888
  var init_loader = () => {};
26350
26889
 
26890
+ // src/relation-utils.ts
26891
+ function normalizeRelationEntry(entry) {
26892
+ if (typeof entry === "string")
26893
+ return { model: entry };
26894
+ if (entry && typeof entry === "object" && typeof entry.model === "string") {
26895
+ const e2 = entry;
26896
+ return { model: e2.model, foreignKey: e2.foreignKey, onDelete: e2.onDelete };
26897
+ }
26898
+ return null;
26899
+ }
26900
+ function normalizeRelationList(rel) {
26901
+ if (!rel)
26902
+ return [];
26903
+ const entries = Array.isArray(rel) ? rel : typeof rel === "object" ? Object.values(rel) : [];
26904
+ return entries.map(normalizeRelationEntry).filter((x2) => x2 !== null);
26905
+ }
26906
+
26351
26907
  // src/meta.ts
26352
26908
  function buildSchemaMeta(models) {
26353
26909
  const modelToTable = {};
@@ -26365,13 +26921,24 @@ function buildSchemaMeta(models) {
26365
26921
  const toRecord = (v2) => {
26366
26922
  if (!v2)
26367
26923
  return {};
26924
+ const rec = {};
26368
26925
  if (Array.isArray(v2)) {
26369
- const rec = {};
26370
- for (const relName of v2)
26371
- rec[relName] = relName;
26926
+ for (const item of v2) {
26927
+ const n2 = normalizeRelationEntry(item);
26928
+ if (n2)
26929
+ rec[n2.model] = n2.model;
26930
+ }
26372
26931
  return rec;
26373
26932
  }
26374
- return v2;
26933
+ if (typeof v2 === "object") {
26934
+ for (const [key, val] of Object.entries(v2)) {
26935
+ const n2 = normalizeRelationEntry(val);
26936
+ if (n2)
26937
+ rec[key] = n2.model;
26938
+ }
26939
+ return rec;
26940
+ }
26941
+ return {};
26375
26942
  };
26376
26943
  const toBelongsToManyRecord = (v2) => {
26377
26944
  if (!v2)
@@ -26418,11 +26985,12 @@ function buildSchemaMeta(models) {
26418
26985
  }
26419
26986
  return { modelToTable, tableToModel, primaryKeys, relations, scopes: scopesByTable, models };
26420
26987
  }
26988
+ var init_meta = () => {};
26421
26989
 
26422
26990
  // src/migrations.ts
26423
26991
  import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync14 } from "fs";
26424
26992
  import { dirname as dirname11, join as join17 } from "path";
26425
- import process35 from "process";
26993
+ import process33 from "process";
26426
26994
  function info2(message) {
26427
26995
  if (config5.verbose)
26428
26996
  console.log(message);
@@ -26438,10 +27006,10 @@ function findWorkspaceRoot6(startPath) {
26438
27006
  }
26439
27007
  currentPath = dirname11(currentPath);
26440
27008
  }
26441
- return process35.cwd();
27009
+ return process33.cwd();
26442
27010
  }
26443
27011
  function ensureSqlDirectory2() {
26444
- const workspaceRoot = findWorkspaceRoot6(process35.cwd());
27012
+ const workspaceRoot = findWorkspaceRoot6(process33.cwd());
26445
27013
  const sqlDir = join17(workspaceRoot, "database", "migrations");
26446
27014
  if (!existsSync21(sqlDir)) {
26447
27015
  mkdirSync9(sqlDir, { recursive: true });
@@ -26580,15 +27148,6 @@ function detectTypeFromValidationRule(rule) {
26580
27148
  }
26581
27149
  return;
26582
27150
  }
26583
- function normalizeBelongsTo(belongsTo) {
26584
- if (!belongsTo)
26585
- return [];
26586
- if (Array.isArray(belongsTo))
26587
- return belongsTo;
26588
- if (typeof belongsTo === "object")
26589
- return Object.values(belongsTo);
26590
- return [];
26591
- }
26592
27151
  function buildMigrationPlan2(models, options) {
26593
27152
  const meta = buildSchemaMeta(models);
26594
27153
  const tables = [];
@@ -26677,12 +27236,12 @@ function buildMigrationPlan2(models, options) {
26677
27236
  }
26678
27237
  columns.push(col);
26679
27238
  }
26680
- const belongsToModels = normalizeBelongsTo(model.belongsTo);
26681
- for (const relatedModel of belongsToModels) {
26682
- const fkColumnName = `${snakeCase(relatedModel)}_id`;
27239
+ const belongsToRelations = normalizeRelationList(model.belongsTo);
27240
+ for (const rel of belongsToRelations) {
27241
+ const fkColumnName = rel.foreignKey ?? `${snakeCase(rel.model)}_id`;
26683
27242
  if (columns.some((c2) => c2.name === fkColumnName))
26684
27243
  continue;
26685
- const refTable = meta.modelToTable[relatedModel];
27244
+ const refTable = meta.modelToTable[rel.model];
26686
27245
  if (!refTable)
26687
27246
  continue;
26688
27247
  const refPk = meta.primaryKeys[refTable] ?? "id";
@@ -26693,7 +27252,7 @@ function buildMigrationPlan2(models, options) {
26693
27252
  isUnique: false,
26694
27253
  isNullable: true,
26695
27254
  hasDefault: false,
26696
- references: { table: refTable, column: refPk }
27255
+ references: { table: refTable, column: refPk, onDelete: rel.onDelete }
26697
27256
  });
26698
27257
  }
26699
27258
  const traits = model.traits;
@@ -27010,6 +27569,13 @@ function columnsAreDifferent(col1, col2) {
27010
27569
  }
27011
27570
  return false;
27012
27571
  }
27572
+ function referencesAreDifferent(r1, r2) {
27573
+ if (Boolean(r1) !== Boolean(r2))
27574
+ return true;
27575
+ if (!r1 || !r2)
27576
+ return false;
27577
+ return r1.table !== r2.table || r1.column !== r2.column || r1.onDelete !== r2.onDelete || r1.onUpdate !== r2.onUpdate;
27578
+ }
27013
27579
  function mapIndexesByKey(indexes) {
27014
27580
  const map = {};
27015
27581
  for (const i2 of indexes) {
@@ -27153,6 +27719,13 @@ function generateDiffSql(previous, next) {
27153
27719
  info2(`-- Detected column type change: ${curr.table}.${colName} (${prevCol.type} -> ${currCol.type})`);
27154
27720
  hasChanges = true;
27155
27721
  }
27722
+ if (referencesAreDifferent(prevCol.references, currCol.references) && currCol.references) {
27723
+ const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
27724
+ tableChanges.push(addFkStatement);
27725
+ chunks.push(addFkStatement);
27726
+ info2(`-- Detected foreign-key change: ${curr.table}.${colName} -> ${currCol.references.table}(${currCol.references.column})${currCol.references.onDelete ? ` ON DELETE ${currCol.references.onDelete}` : ""}`);
27727
+ hasChanges = true;
27728
+ }
27156
27729
  }
27157
27730
  }
27158
27731
  for (const colName of Object.keys(currCols)) {
@@ -27206,6 +27779,7 @@ var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0
27206
27779
  var init_migrations = __esm(() => {
27207
27780
  init_config();
27208
27781
  init_drivers();
27782
+ init_meta();
27209
27783
  });
27210
27784
 
27211
27785
  // src/schema.ts
@@ -27240,6 +27814,7 @@ var init_src2 = __esm(() => {
27240
27814
  init_dynamodb_tooling_adapter();
27241
27815
  init_dynamodb();
27242
27816
  init_loader();
27817
+ init_meta();
27243
27818
  init_migrations();
27244
27819
  init_orm();
27245
27820
  });
@@ -27249,7 +27824,7 @@ import { existsSync as existsSync23 } from "fs";
27249
27824
  import fs from "fs/promises";
27250
27825
  import os from "os";
27251
27826
  import path from "path";
27252
- import process36 from "process";
27827
+ import process35 from "process";
27253
27828
  import { EventEmitter } from "events";
27254
27829
  import process52 from "process";
27255
27830
  import process210 from "process";
@@ -27273,10 +27848,10 @@ class Telemetry {
27273
27848
  this.configPath = path.join(configDir, "telemetry.json");
27274
27849
  }
27275
27850
  async isEnabled() {
27276
- if (process36.env.DO_NOT_TRACK === "1" || process36.env.DO_NOT_TRACK === "true") {
27851
+ if (process35.env.DO_NOT_TRACK === "1" || process35.env.DO_NOT_TRACK === "true") {
27277
27852
  return false;
27278
27853
  }
27279
- if (process36.env.NO_TELEMETRY === "1" || process36.env.NO_TELEMETRY === "true") {
27854
+ if (process35.env.NO_TELEMETRY === "1" || process35.env.NO_TELEMETRY === "true") {
27280
27855
  return false;
27281
27856
  }
27282
27857
  const config6 = await this.loadConfig();
@@ -27305,7 +27880,7 @@ class Telemetry {
27305
27880
  ...data,
27306
27881
  timestamp: Date.now(),
27307
27882
  platform: os.platform(),
27308
- nodeVersion: process36.version
27883
+ nodeVersion: process35.version
27309
27884
  };
27310
27885
  this.events.push(telemetryEvent);
27311
27886
  if (this.events.length >= 10) {
@@ -27361,7 +27936,7 @@ class Telemetry {
27361
27936
  const config6 = await this.loadConfig();
27362
27937
  return {
27363
27938
  enabled: config6.enabled,
27364
- doNotTrack: process36.env.DO_NOT_TRACK === "1" || process36.env.DO_NOT_TRACK === "true",
27939
+ doNotTrack: process35.env.DO_NOT_TRACK === "1" || process35.env.DO_NOT_TRACK === "true",
27365
27940
  eventsQueued: this.events.length,
27366
27941
  lastSent: config6.lastSent
27367
27942
  };
@@ -29283,7 +29858,7 @@ function getPrefix() {
29283
29858
  }
29284
29859
  var prefix = getPrefix();
29285
29860
  // package.json
29286
- var version2 = "0.1.25";
29861
+ var version2 = "0.1.26";
29287
29862
 
29288
29863
  // bin/cli.ts
29289
29864
  init_actions();
@@ -29309,6 +29884,15 @@ var cli = new CLI("query-builder");
29309
29884
  cli.command("introspect <dir>", "Load models and print inferred schema").option("--verbose", "Enable verbose logging").example("query-builder introspect ./app/Models --verbose").action(async (dir, _options) => {
29310
29885
  await introspect(dir, _options);
29311
29886
  });
29887
+ cli.command("introspect:db", "Reverse-introspect the live database into defineModel() source (#1047)").option("--table <table>", "Limit to a single table (repeatable)").example("query-builder introspect:db > app/Models/generated.ts").action(async (_options) => {
29888
+ const t2 = _options?.table;
29889
+ const tables = t2 ? Array.isArray(t2) ? t2 : [t2] : undefined;
29890
+ const models = await introspectDatabase({ tables });
29891
+ console.log(`import { defineModel } from 'bun-query-builder'
29892
+
29893
+ ${models.map((m2) => m2.source).join(`
29894
+ `)}`);
29895
+ });
29312
29896
  cli.command("sql <dir> <table>", "Build a sample query for a table").option("--limit <n>", "Limit rows", { default: 10 }).example("query-builder sql ./app/Models users --limit 5").action(async (dir, table, opts) => {
29313
29897
  await sql(dir, table, opts);
29314
29898
  });