bun-query-builder 0.1.20 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -11663,13 +11663,22 @@ async function getConfig() {
11663
11663
  return _config;
11664
11664
  }
11665
11665
  function setConfig(userConfig) {
11666
- if (config3 == null || config3.dialect === undefined) { config3 = defaultConfig3 ? { ...defaultConfig3 } : {} }
11667
- Object.assign(config3, userConfig);
11668
- if (userConfig.database) { config3.database = { ...config3.database, ...userConfig.database }; }
11669
- if (userConfig.timestamps) { config3.timestamps = { ...config3.timestamps, ...userConfig.timestamps }; }
11670
- if (userConfig.pagination) { config3.pagination = { ...config3.pagination, ...userConfig.pagination }; }
11671
- if (userConfig.softDeletes) { config3.softDeletes = { ...config3.softDeletes, ...userConfig.softDeletes }; }
11672
- if (_config) { Object.assign(_config, config3); }
11666
+ Object.assign(config5, userConfig);
11667
+ if (userConfig.database) {
11668
+ config5.database = { ...config5.database, ...userConfig.database };
11669
+ }
11670
+ if (userConfig.timestamps) {
11671
+ config5.timestamps = { ...config5.timestamps, ...userConfig.timestamps };
11672
+ }
11673
+ if (userConfig.pagination) {
11674
+ config5.pagination = { ...config5.pagination, ...userConfig.pagination };
11675
+ }
11676
+ if (userConfig.softDeletes) {
11677
+ config5.softDeletes = { ...config5.softDeletes, ...userConfig.softDeletes };
11678
+ }
11679
+ if (_config) {
11680
+ Object.assign(_config, config5);
11681
+ }
11673
11682
  }
11674
11683
  var defaultConfig4, config5, _config = null;
11675
11684
  var init_config = __esm(() => {
@@ -12368,6 +12377,7 @@ function createQueryBuilder(state) {
12368
12377
  function applyCondition(expr) {
12369
12378
  if (Array.isArray(expr)) {
12370
12379
  const [col, op, val] = expr;
12380
+ validateIdentifier(col, "where(column)");
12371
12381
  const colName = String(col);
12372
12382
  switch (op) {
12373
12383
  case "in":
@@ -12385,9 +12395,12 @@ function createQueryBuilder(state) {
12385
12395
  case "like":
12386
12396
  return _sql.unsafe(`${colName} LIKE ${getPlaceholder(1)}`, [val]);
12387
12397
  case "is":
12388
- return _sql.unsafe(`${colName} IS ${val}`);
12389
- case "is not":
12390
- return _sql.unsafe(`${colName} IS NOT ${val}`);
12398
+ case "is not": {
12399
+ if (val !== null && val !== undefined) {
12400
+ throw new TypeError(`[query-builder] where(..., '${op}', ?): operator '${op}' only accepts NULL/undefined as value, got ${typeof val} (${String(val)})`);
12401
+ }
12402
+ return _sql.unsafe(`${colName} IS ${op === "is not" ? "NOT " : ""}NULL`);
12403
+ }
12391
12404
  case "!=":
12392
12405
  return _sql.unsafe(`${colName} <> ${getPlaceholder(1)}`, [val]);
12393
12406
  case "<":
@@ -12395,8 +12408,9 @@ function createQueryBuilder(state) {
12395
12408
  case "<=":
12396
12409
  case ">=":
12397
12410
  case "=":
12398
- default:
12399
12411
  return _sql.unsafe(`${colName} ${op} ${getPlaceholder(1)}`, [val]);
12412
+ default:
12413
+ throw new TypeError(`[query-builder] where(..., '${String(op)}', ?): unsupported operator. Allowed: =, !=, <>, <, <=, >, >=, like, in, not in, is, is not`);
12400
12414
  }
12401
12415
  }
12402
12416
  if ("raw" in expr) {
@@ -12409,6 +12423,7 @@ function createQueryBuilder(state) {
12409
12423
  const allParams = [];
12410
12424
  let paramIndex = 1;
12411
12425
  for (const key of keys) {
12426
+ validateIdentifier(key, "where(object key)");
12412
12427
  const value = expr[key];
12413
12428
  if (Array.isArray(value)) {
12414
12429
  const placeholders = getPlaceholders(value.length, paramIndex);
@@ -12597,17 +12612,17 @@ function createQueryBuilder(state) {
12597
12612
  return;
12598
12613
  const parentTable = String(table);
12599
12614
  const parentPk = meta.primaryKeys[parentTable] ?? "id";
12600
- validateIdentifier(resolved.pivotTable, "wherePivot auto-join (pivot table)");
12601
- validateIdentifier(resolved.fkParent, "wherePivot auto-join (parent FK)");
12602
- validateIdentifier(parentTable, "wherePivot auto-join (parent table)");
12603
- validateIdentifier(parentPk, "wherePivot auto-join (parent PK)");
12615
+ validateIdentifier2(resolved.pivotTable, "wherePivot auto-join (pivot table)");
12616
+ validateIdentifier2(resolved.fkParent, "wherePivot auto-join (parent FK)");
12617
+ validateIdentifier2(parentTable, "wherePivot auto-join (parent table)");
12618
+ validateIdentifier2(parentPk, "wherePivot auto-join (parent PK)");
12604
12619
  built = sql`${ensureBuilt()} LEFT JOIN ${sql(resolved.pivotTable)} ON ${sql(`${resolved.pivotTable}.${resolved.fkParent}`)} = ${sql(`${parentTable}.${parentPk}`)}`;
12605
12620
  text = `${text} LEFT JOIN ${resolved.pivotTable} ON ${resolved.pivotTable}.${resolved.fkParent} = ${parentTable}.${parentPk}`;
12606
12621
  joinedTables.add(resolved.pivotTable);
12607
12622
  };
12608
12623
  const whereConditions = [];
12609
12624
  const whereParams = [];
12610
- const validateIdentifier = (name, context) => {
12625
+ const validateIdentifier2 = (name, context) => {
12611
12626
  if (!SQL_PATTERNS.IDENTIFIER.test(name)) {
12612
12627
  const contextMsg = context ? ` in ${context}` : "";
12613
12628
  throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
@@ -12631,18 +12646,56 @@ function createQueryBuilder(state) {
12631
12646
  }
12632
12647
  }
12633
12648
  };
12649
+ const SAFE_WHERE_OPERATORS = new Set([
12650
+ "=",
12651
+ "!=",
12652
+ "<>",
12653
+ "<",
12654
+ "<=",
12655
+ ">",
12656
+ ">=",
12657
+ "like",
12658
+ "not like",
12659
+ "ilike",
12660
+ "not ilike",
12661
+ "in",
12662
+ "not in",
12663
+ "is",
12664
+ "is not",
12665
+ "between",
12666
+ "not between"
12667
+ ]);
12668
+ function assertSafeWhereOperator(op, context) {
12669
+ if (typeof op !== "string")
12670
+ throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
12671
+ const lower = op.toLowerCase();
12672
+ if (!SAFE_WHERE_OPERATORS.has(lower))
12673
+ throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
12674
+ return op;
12675
+ }
12676
+ function formatSubqueryValue(val) {
12677
+ if (val === null)
12678
+ return "NULL";
12679
+ if (typeof val === "number" && Number.isFinite(val))
12680
+ return String(val);
12681
+ if (typeof val === "boolean")
12682
+ return val ? "1" : "0";
12683
+ if (typeof val === "string")
12684
+ return `'${val.replace(/'/g, "''")}'`;
12685
+ throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
12686
+ }
12634
12687
  const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
12635
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12636
- validateIdentifier(targetTable, "relationship subquery (target table)");
12637
- validateIdentifier(pk, "relationship subquery (primary key)");
12688
+ validateIdentifier2(parentTable, "relationship subquery (parent table)");
12689
+ validateIdentifier2(targetTable, "relationship subquery (target table)");
12690
+ validateIdentifier2(pk, "relationship subquery (primary key)");
12638
12691
  const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
12639
- validateIdentifier(fk, "relationship subquery (foreign key)");
12692
+ validateIdentifier2(fk, "relationship subquery (foreign key)");
12640
12693
  let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk}`;
12641
12694
  if (callback) {
12642
12695
  const subQb = {
12643
12696
  where: (col, op, val) => {
12644
- validateIdentifier(col, "relationship subquery condition");
12645
- return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
12697
+ validateIdentifier2(col, "relationship subquery condition");
12698
+ return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
12646
12699
  }
12647
12700
  };
12648
12701
  const condition = callback(subQb);
@@ -12653,16 +12706,16 @@ function createQueryBuilder(state) {
12653
12706
  return subquerySQL;
12654
12707
  };
12655
12708
  const buildBelongsToSubquery = (parentTable, targetTable, pk, callback) => {
12656
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12657
- validateIdentifier(targetTable, "relationship subquery (target table)");
12658
- validateIdentifier(pk, "relationship subquery (primary key)");
12709
+ validateIdentifier2(parentTable, "relationship subquery (parent table)");
12710
+ validateIdentifier2(targetTable, "relationship subquery (target table)");
12711
+ validateIdentifier2(pk, "relationship subquery (primary key)");
12659
12712
  const fk = `${targetTable.endsWith("s") ? targetTable.slice(0, -1) : targetTable}_id`;
12660
- validateIdentifier(fk, "relationship subquery (foreign key)");
12713
+ validateIdentifier2(fk, "relationship subquery (foreign key)");
12661
12714
  let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${pk} = ${parentTable}.${fk}`;
12662
12715
  if (callback) {
12663
12716
  const subQb = {
12664
12717
  where: (col, op, val) => {
12665
- validateIdentifier(col, "relationship subquery condition");
12718
+ validateIdentifier2(col, "relationship subquery condition");
12666
12719
  return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
12667
12720
  }
12668
12721
  };
@@ -12674,24 +12727,24 @@ function createQueryBuilder(state) {
12674
12727
  return subquerySQL;
12675
12728
  };
12676
12729
  const buildBelongsToManySubquery = (parentTable, targetTable, pk, targetPk, callback, relationKey) => {
12677
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12678
- validateIdentifier(targetTable, "relationship subquery (target table)");
12679
- validateIdentifier(pk, "relationship subquery (primary key)");
12680
- validateIdentifier(targetPk, "relationship subquery (target primary key)");
12730
+ validateIdentifier2(parentTable, "relationship subquery (parent table)");
12731
+ validateIdentifier2(targetTable, "relationship subquery (target table)");
12732
+ validateIdentifier2(pk, "relationship subquery (primary key)");
12733
+ validateIdentifier2(targetPk, "relationship subquery (target primary key)");
12681
12734
  const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
12682
12735
  const a = singularize(parentTable);
12683
12736
  const b = singularize(targetTable);
12684
12737
  const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
12685
12738
  const fkA = resolved?.fkParent ?? `${a}_id`;
12686
12739
  const fkB = resolved?.fkRelated ?? `${b}_id`;
12687
- validateIdentifier(pivot, "relationship subquery (pivot table)");
12688
- validateIdentifier(fkA, "relationship subquery (foreign key A)");
12689
- validateIdentifier(fkB, "relationship subquery (foreign key B)");
12740
+ validateIdentifier2(pivot, "relationship subquery (pivot table)");
12741
+ validateIdentifier2(fkA, "relationship subquery (foreign key A)");
12742
+ validateIdentifier2(fkB, "relationship subquery (foreign key B)");
12690
12743
  let subquerySQL = `SELECT 1 FROM ${pivot} JOIN ${targetTable} ON ${targetTable}.${targetPk} = ${pivot}.${fkB} WHERE ${pivot}.${fkA} = ${parentTable}.${pk}`;
12691
12744
  if (callback) {
12692
12745
  const subQb = {
12693
12746
  where: (col, op, val) => {
12694
- validateIdentifier(col, "relationship subquery condition");
12747
+ validateIdentifier2(col, "relationship subquery condition");
12695
12748
  return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
12696
12749
  }
12697
12750
  };
@@ -12703,24 +12756,24 @@ function createQueryBuilder(state) {
12703
12756
  return subquerySQL;
12704
12757
  };
12705
12758
  const buildHasCountSubquery = (parentTable, targetTable, pk) => {
12706
- validateIdentifier(parentTable, "withCount (parent table)");
12707
- validateIdentifier(targetTable, "withCount (target table)");
12708
- validateIdentifier(pk, "withCount (primary key)");
12759
+ validateIdentifier2(parentTable, "withCount (parent table)");
12760
+ validateIdentifier2(targetTable, "withCount (target table)");
12761
+ validateIdentifier2(pk, "withCount (primary key)");
12709
12762
  const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
12710
- validateIdentifier(fk, "withCount (foreign key)");
12763
+ validateIdentifier2(fk, "withCount (foreign key)");
12711
12764
  return `(SELECT COUNT(*) FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk})`;
12712
12765
  };
12713
12766
  const buildBelongsToManyCountSubquery = (parentTable, targetTable, pk, relationKey) => {
12714
- validateIdentifier(parentTable, "withCount (parent table)");
12715
- validateIdentifier(targetTable, "withCount (target table)");
12716
- validateIdentifier(pk, "withCount (primary key)");
12767
+ validateIdentifier2(parentTable, "withCount (parent table)");
12768
+ validateIdentifier2(targetTable, "withCount (target table)");
12769
+ validateIdentifier2(pk, "withCount (primary key)");
12717
12770
  const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
12718
12771
  const a = singularize(parentTable);
12719
12772
  const b = singularize(targetTable);
12720
12773
  const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
12721
12774
  const fkA = resolved?.fkParent ?? `${a}_id`;
12722
- validateIdentifier(pivot, "withCount (pivot table)");
12723
- validateIdentifier(fkA, "withCount (foreign key)");
12775
+ validateIdentifier2(pivot, "withCount (pivot table)");
12776
+ validateIdentifier2(fkA, "withCount (foreign key)");
12724
12777
  return `(SELECT COUNT(*) FROM ${pivot} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
12725
12778
  };
12726
12779
  const applyPivotColumnsToQuery = () => {
@@ -12732,7 +12785,7 @@ function createQueryBuilder(state) {
12732
12785
  if (!resolved)
12733
12786
  continue;
12734
12787
  for (const col of columns2) {
12735
- validateIdentifier(col, "withPivot");
12788
+ validateIdentifier2(col, "withPivot");
12736
12789
  }
12737
12790
  const pivotColumnsStr = columns2.map((col) => `${resolved.pivotTable}.${col} AS pivot_${col}`);
12738
12791
  allPivotColumns.push(...pivotColumnsStr);
@@ -13282,8 +13335,8 @@ function createQueryBuilder(state) {
13282
13335
  if (!resolved) {
13283
13336
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13284
13337
  }
13285
- validateIdentifier(resolved.pivotTable, "wherePivot (pivot table)");
13286
- validateIdentifier(column, "wherePivot (column)");
13338
+ validateIdentifier2(resolved.pivotTable, "wherePivot (pivot table)");
13339
+ validateIdentifier2(column, "wherePivot (column)");
13287
13340
  ensurePivotJoined(resolved);
13288
13341
  const op = value === undefined ? "=" : String(opOrValue);
13289
13342
  const val = value === undefined ? opOrValue : value;
@@ -13305,8 +13358,8 @@ function createQueryBuilder(state) {
13305
13358
  if (!resolved) {
13306
13359
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13307
13360
  }
13308
- validateIdentifier(resolved.pivotTable, "wherePivotIn (pivot table)");
13309
- validateIdentifier(column, "wherePivotIn (column)");
13361
+ validateIdentifier2(resolved.pivotTable, "wherePivotIn (pivot table)");
13362
+ validateIdentifier2(column, "wherePivotIn (column)");
13310
13363
  ensurePivotJoined(resolved);
13311
13364
  const placeholders = getPlaceholders(values.length, whereParams.length + 1);
13312
13365
  const clause = `${resolved.pivotTable}.${column} IN (${placeholders})`;
@@ -13326,8 +13379,8 @@ function createQueryBuilder(state) {
13326
13379
  if (!resolved) {
13327
13380
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13328
13381
  }
13329
- validateIdentifier(resolved.pivotTable, "wherePivotNotIn (pivot table)");
13330
- validateIdentifier(column, "wherePivotNotIn (column)");
13382
+ validateIdentifier2(resolved.pivotTable, "wherePivotNotIn (pivot table)");
13383
+ validateIdentifier2(column, "wherePivotNotIn (column)");
13331
13384
  ensurePivotJoined(resolved);
13332
13385
  const placeholders = getPlaceholders(values.length, whereParams.length + 1);
13333
13386
  const clause = `${resolved.pivotTable}.${column} NOT IN (${placeholders})`;
@@ -13347,8 +13400,8 @@ function createQueryBuilder(state) {
13347
13400
  if (!resolved) {
13348
13401
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13349
13402
  }
13350
- validateIdentifier(resolved.pivotTable, "wherePivotNull (pivot table)");
13351
- validateIdentifier(column, "wherePivotNull (column)");
13403
+ validateIdentifier2(resolved.pivotTable, "wherePivotNull (pivot table)");
13404
+ validateIdentifier2(column, "wherePivotNull (column)");
13352
13405
  ensurePivotJoined(resolved);
13353
13406
  const clause = `${resolved.pivotTable}.${column} IS NULL`;
13354
13407
  whereConditions.push(clause);
@@ -13366,8 +13419,8 @@ function createQueryBuilder(state) {
13366
13419
  if (!resolved) {
13367
13420
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13368
13421
  }
13369
- validateIdentifier(resolved.pivotTable, "wherePivotNotNull (pivot table)");
13370
- validateIdentifier(column, "wherePivotNotNull (column)");
13422
+ validateIdentifier2(resolved.pivotTable, "wherePivotNotNull (pivot table)");
13423
+ validateIdentifier2(column, "wherePivotNotNull (column)");
13371
13424
  ensurePivotJoined(resolved);
13372
13425
  const clause = `${resolved.pivotTable}.${column} IS NOT NULL`;
13373
13426
  whereConditions.push(clause);
@@ -13600,10 +13653,14 @@ function createQueryBuilder(state) {
13600
13653
  return this;
13601
13654
  },
13602
13655
  whereDate(column, op, date) {
13656
+ validateIdentifier2(column, "whereDate(column)");
13657
+ const dateString = date instanceof Date ? date.toISOString() : typeof date === "string" ? date : (() => {
13658
+ throw new TypeError(`[query-builder] whereDate(date): expected string or Date, got ${typeof date}`);
13659
+ })();
13603
13660
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13604
13661
  const idx = whereParams.length + 1;
13605
13662
  text += ` ${keyword} ${column} ${op} ${getPlaceholder(idx)}`;
13606
- whereParams.push(String(date));
13663
+ whereParams.push(dateString);
13607
13664
  built = null;
13608
13665
  return this;
13609
13666
  },
@@ -13614,12 +13671,16 @@ function createQueryBuilder(state) {
13614
13671
  return this;
13615
13672
  },
13616
13673
  whereColumn(left, op, right) {
13674
+ validateIdentifier2(left, "whereColumn(left)");
13675
+ validateIdentifier2(right, "whereColumn(right)");
13617
13676
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13618
13677
  text += ` ${keyword} ${left} ${op} ${right}`;
13619
13678
  built = null;
13620
13679
  return this;
13621
13680
  },
13622
13681
  orWhereColumn(left, op, right) {
13682
+ validateIdentifier2(left, "orWhereColumn(left)");
13683
+ validateIdentifier2(right, "orWhereColumn(right)");
13623
13684
  text += ` OR ${left} ${op} ${right}`;
13624
13685
  built = null;
13625
13686
  return this;
@@ -13839,11 +13900,15 @@ function createQueryBuilder(state) {
13839
13900
  return this;
13840
13901
  },
13841
13902
  limit(n) {
13903
+ if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n))
13904
+ throw new TypeError(`[bun-query-builder] limit(n): expected non-negative integer, got ${n}`);
13842
13905
  text = SQL_PATTERNS.LIMIT.test(text) ? text.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`) : `${text} LIMIT ${n}`;
13843
13906
  built = null;
13844
13907
  return this;
13845
13908
  },
13846
13909
  offset(n) {
13910
+ if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n))
13911
+ throw new TypeError(`[bun-query-builder] offset(n): expected non-negative integer, got ${n}`);
13847
13912
  text = SQL_PATTERNS.OFFSET.test(text) ? text.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`) : `${text} OFFSET ${n}`;
13848
13913
  built = null;
13849
13914
  return this;
@@ -14116,19 +14181,25 @@ function createQueryBuilder(state) {
14116
14181
  includeTrashed = true;
14117
14182
  onlyTrashed = true;
14118
14183
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14119
- if (text.includes("WHERE")) {
14120
- text = text.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
14121
- } else {
14122
- text = `${text} WHERE ${table}.${softDeleteColumn} IS NOT NULL`;
14123
- }
14184
+ const splice = (raw, predicate2) => {
14185
+ const upper = raw.toUpperCase();
14186
+ let depth = 0;
14187
+ for (let i = 0;i < raw.length; i++) {
14188
+ const c = raw[i];
14189
+ if (c === "(")
14190
+ depth++;
14191
+ else if (c === ")")
14192
+ depth--;
14193
+ else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
14194
+ return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
14195
+ }
14196
+ }
14197
+ return `${raw} WHERE ${predicate2}`;
14198
+ };
14199
+ const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
14200
+ text = splice(text, predicate);
14124
14201
  const currentSql = String(ensureBuilt());
14125
- if (currentSql.includes("WHERE")) {
14126
- const newSql = currentSql.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
14127
- built = sql([newSql]);
14128
- } else {
14129
- const newSql = `${currentSql} WHERE ${table}.${softDeleteColumn} IS NOT NULL`;
14130
- built = sql([newSql]);
14131
- }
14202
+ built = sql([splice(currentSql, predicate)]);
14132
14203
  return this;
14133
14204
  },
14134
14205
  scope(name, value) {
@@ -14302,7 +14373,15 @@ function createQueryBuilder(state) {
14302
14373
  },
14303
14374
  async count() {
14304
14375
  const fromIdx = text.indexOf(" FROM ");
14305
- const countText = fromIdx !== -1 ? `SELECT COUNT(*) as c${text.substring(fromIdx)}` : `SELECT COUNT(*) as c FROM ${table}`;
14376
+ const hasGroupBy = / GROUP BY /i.test(text);
14377
+ let countText;
14378
+ if (hasGroupBy) {
14379
+ countText = `SELECT COUNT(*) as c FROM (${text}) AS _bqb_count_sub`;
14380
+ } else if (fromIdx !== -1) {
14381
+ countText = `SELECT COUNT(*) as c${text.substring(fromIdx)}`;
14382
+ } else {
14383
+ countText = `SELECT COUNT(*) as c FROM ${table}`;
14384
+ }
14306
14385
  const cHooks = config5.hooks;
14307
14386
  const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan);
14308
14387
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
@@ -14726,7 +14805,7 @@ function createQueryBuilder(state) {
14726
14805
  let sqlText = "";
14727
14806
  const params = [];
14728
14807
  const isPostgres = config5.dialect === "postgres";
14729
- const quoteId = isPostgres ? (id) => `"${id}"` : config5.dialect === "mysql" ? (id) => `\`${id}\`` : (id) => id;
14808
+ const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : config5.dialect === "mysql" ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
14730
14809
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
14731
14810
  return {
14732
14811
  values(data) {
@@ -14859,11 +14938,11 @@ function createQueryBuilder(state) {
14859
14938
  updateTable(table) {
14860
14939
  let built;
14861
14940
  const params = [];
14862
- const quoteId = (id) => {
14863
- if (config5.dialect === "mysql") {
14864
- return `\`${id}\``;
14865
- }
14866
- return `"${id}"`;
14941
+ const quoteId = (identifier) => {
14942
+ const s = String(identifier);
14943
+ if (config5.dialect === "mysql")
14944
+ return `\`${s.replace(/`/g, "``")}\``;
14945
+ return `"${s.replace(/"/g, '""')}"`;
14867
14946
  };
14868
14947
  let sqlText = `UPDATE ${quoteId(String(table))}`;
14869
14948
  return {
@@ -14967,11 +15046,11 @@ function createQueryBuilder(state) {
14967
15046
  };
14968
15047
  },
14969
15048
  deleteFrom(table) {
14970
- const quoteId = (id) => {
14971
- if (config5.dialect === "mysql") {
14972
- return `\`${id}\``;
14973
- }
14974
- return `"${id}"`;
15049
+ const quoteId = (identifier) => {
15050
+ const s = String(identifier);
15051
+ if (config5.dialect === "mysql")
15052
+ return `\`${s.replace(/`/g, "``")}\``;
15053
+ return `"${s.replace(/"/g, '""')}"`;
14975
15054
  };
14976
15055
  const quotedTable = quoteId(String(table));
14977
15056
  let sqlText = `DELETE FROM ${quotedTable}`;
@@ -15255,6 +15334,10 @@ function createQueryBuilder(state) {
15255
15334
  };
15256
15335
  },
15257
15336
  async insertOrIgnore(table, values) {
15337
+ if (config5.dialect === "mysql") {
15338
+ const built2 = bunSql`INSERT IGNORE INTO ${bunSql(String(table))} ${bunSql(values)}`;
15339
+ return built2.execute();
15340
+ }
15258
15341
  const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} ON CONFLICT DO NOTHING`;
15259
15342
  return built.execute();
15260
15343
  },
@@ -15290,7 +15373,15 @@ function createQueryBuilder(state) {
15290
15373
  async upsert(table, rows, conflictColumns, mergeColumns) {
15291
15374
  const targetCols = conflictColumns.map((c) => String(c));
15292
15375
  const setCols = (mergeColumns ?? []).map((c) => String(c));
15293
- const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON CONFLICT (${bunSql(targetCols)}) DO UPDATE SET ${bunSql(setCols.reduce((acc, c) => ({ ...acc, [c]: bunSql(`EXCLUDED.${c}`) }), {}))}`;
15376
+ if (config5.dialect === "mysql") {
15377
+ const updateList2 = setCols.map((c) => `\`${c.replace(/`/g, "``")}\` = VALUES(\`${c.replace(/`/g, "``")}\`)`).join(", ");
15378
+ const built2 = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON DUPLICATE KEY UPDATE ${bunSql.unsafe(updateList2)}`;
15379
+ return built2.execute();
15380
+ }
15381
+ const isPostgres = config5.dialect === "postgres";
15382
+ const quoteCol = (column) => isPostgres ? `"${column.replace(/"/g, '""')}"` : `"${column.replace(/"/g, '""')}"`;
15383
+ const updateList = setCols.map((column) => `${quoteCol(column)} = EXCLUDED.${quoteCol(column)}`).join(", ");
15384
+ const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON CONFLICT (${bunSql(targetCols)}) DO UPDATE SET ${bunSql.unsafe(updateList)}`;
15294
15385
  return built.execute();
15295
15386
  },
15296
15387
  async save(table, values) {
@@ -24055,6 +24146,14 @@ var init_src = __esm(async () => {
24055
24146
 
24056
24147
  // src/orm.ts
24057
24148
  import { Database as Database2 } from "bun:sqlite";
24149
+ function assertValidIdentifier(name, context) {
24150
+ if (typeof name !== "string" || name.length === 0)
24151
+ throw new TypeError(`[bun-query-builder] ${context}: identifier must be a non-empty string, got ${typeof name}`);
24152
+ if (name.length > 64)
24153
+ throw new TypeError(`[bun-query-builder] ${context}: identifier '${name}' exceeds 64 chars`);
24154
+ if (!SAFE_SQL_IDENTIFIER.test(name))
24155
+ throw new TypeError(`[bun-query-builder] ${context}: identifier '${name}' contains characters outside [A-Za-z0-9_] \u2014 refusing to interpolate into SQL`);
24156
+ }
24058
24157
  function getModelFromRegistry(name) {
24059
24158
  if (!_getModel) {
24060
24159
  try {
@@ -24605,14 +24704,21 @@ class BelongsToManyRelationBuilder {
24605
24704
  return this;
24606
24705
  }
24607
24706
  orderBy(column, direction = "asc") {
24707
+ assertValidIdentifier(column, "orderBy(column)");
24708
+ if (direction !== "asc" && direction !== "desc")
24709
+ throw new TypeError(`[bun-query-builder] orderBy(direction): expected 'asc' or 'desc', got '${direction}'`);
24608
24710
  this._orderBy.push(`${column} ${direction.toUpperCase()}`);
24609
24711
  return this;
24610
24712
  }
24611
24713
  limit(n2) {
24714
+ if (!Number.isFinite(n2) || n2 < 0 || !Number.isInteger(n2))
24715
+ throw new TypeError(`[bun-query-builder] limit(n): expected non-negative integer, got ${n2}`);
24612
24716
  this._limit = n2;
24613
24717
  return this;
24614
24718
  }
24615
24719
  offset(n2) {
24720
+ if (!Number.isFinite(n2) || n2 < 0 || !Number.isInteger(n2))
24721
+ throw new TypeError(`[bun-query-builder] offset(n): expected non-negative integer, got ${n2}`);
24616
24722
  this._offset = n2;
24617
24723
  return this;
24618
24724
  }
@@ -24839,18 +24945,22 @@ class ModelQueryBuilder {
24839
24945
  return this;
24840
24946
  }
24841
24947
  whereNull(column) {
24948
+ assertValidIdentifier(column, "whereNull(column)");
24842
24949
  this._wheres.push({ column, operator: "=", value: null, boolean: "and" });
24843
24950
  return this;
24844
24951
  }
24845
24952
  orWhereNull(column) {
24953
+ assertValidIdentifier(column, "orWhereNull(column)");
24846
24954
  this._wheres.push({ column, operator: "=", value: null, boolean: "or" });
24847
24955
  return this;
24848
24956
  }
24849
24957
  whereNotNull(column) {
24958
+ assertValidIdentifier(column, "whereNotNull(column)");
24850
24959
  this._wheres.push({ column, operator: "!=", value: null, boolean: "and" });
24851
24960
  return this;
24852
24961
  }
24853
24962
  orWhereNotNull(column) {
24963
+ assertValidIdentifier(column, "orWhereNotNull(column)");
24854
24964
  this._wheres.push({ column, operator: "!=", value: null, boolean: "or" });
24855
24965
  return this;
24856
24966
  }
@@ -24897,13 +25007,19 @@ class ModelQueryBuilder {
24897
25007
  return this;
24898
25008
  }
24899
25009
  whereBetween(column, range) {
25010
+ assertValidIdentifier(column, "whereBetween(column)");
24900
25011
  this._wheres.push({ column, operator: ">=", value: range[0], boolean: "and" });
24901
25012
  this._wheres.push({ column, operator: "<=", value: range[1], boolean: "and" });
24902
25013
  return this;
24903
25014
  }
24904
25015
  whereNotBetween(column, range) {
24905
- this._wheres.push({ column, operator: "<", value: range[0], boolean: "and" });
24906
- this._wheres.push({ column, operator: ">", value: range[1], boolean: "or" });
25016
+ assertValidIdentifier(column, "whereNotBetween(column)");
25017
+ const col = column;
25018
+ this._wheres.push({
25019
+ raw: `(${col} < ? OR ${col} > ?)`,
25020
+ rawParams: [range[0], range[1]],
25021
+ boolean: "and"
25022
+ });
24907
25023
  return this;
24908
25024
  }
24909
25025
  when(condition, callback) {
@@ -24913,6 +25029,9 @@ class ModelQueryBuilder {
24913
25029
  return this;
24914
25030
  }
24915
25031
  orderBy(column, direction = "asc") {
25032
+ assertValidIdentifier(column, "orderBy(column)");
25033
+ if (direction !== "asc" && direction !== "desc")
25034
+ throw new TypeError(`[bun-query-builder] orderBy(direction): expected 'asc' or 'desc', got '${direction}'`);
24916
25035
  this._orderBy.push({ column, direction });
24917
25036
  return this;
24918
25037
  }
@@ -25157,6 +25276,7 @@ class ModelQueryBuilder {
25157
25276
  return results[0];
25158
25277
  }
25159
25278
  increment(column, amount = 1) {
25279
+ assertValidIdentifier(column, "increment(column)");
25160
25280
  const db = getDatabase();
25161
25281
  const params = [amount];
25162
25282
  let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
@@ -25213,6 +25333,7 @@ class ModelQueryBuilder {
25213
25333
  };
25214
25334
  }
25215
25335
  pluck(column) {
25336
+ assertValidIdentifier(column, "pluck(column)");
25216
25337
  const db = getDatabase();
25217
25338
  const params = [];
25218
25339
  let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
@@ -25230,6 +25351,7 @@ class ModelQueryBuilder {
25230
25351
  return rows.map((r2) => r2[column]);
25231
25352
  }
25232
25353
  aggregate(fn, column) {
25354
+ assertValidIdentifier(column, `${fn}(column)`);
25233
25355
  const db = getDatabase();
25234
25356
  const params = [];
25235
25357
  let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
@@ -25506,8 +25628,9 @@ async function seedModel(definition, count, faker) {
25506
25628
  db.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
25507
25629
  }
25508
25630
  }
25509
- var _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
25631
+ var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
25510
25632
  var init_orm = __esm(() => {
25633
+ SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
25511
25634
  snakeCaseCache = new Map;
25512
25635
  tableNameCache = new Map;
25513
25636
  relationCache = new Map;
@@ -25517,6 +25640,7 @@ var init_orm = __esm(() => {
25517
25640
  // src/model.ts
25518
25641
  var exports_model = {};
25519
25642
  __export(exports_model, {
25643
+ registerModel: () => registerModel,
25520
25644
  registerBrowserModels: () => registerBrowserModels,
25521
25645
  hasModel: () => hasModel,
25522
25646
  getModelRegistry: () => getModelRegistry,
@@ -25540,6 +25664,10 @@ function hasModel(name) {
25540
25664
  function clearModelRegistry() {
25541
25665
  modelRegistry.clear();
25542
25666
  }
25667
+ function registerModel(name, model) {
25668
+ modelRegistry.set(name, model);
25669
+ return model;
25670
+ }
25543
25671
  function isClientSide() {
25544
25672
  return typeof window !== "undefined" && typeof document !== "undefined";
25545
25673
  }
@@ -25556,7 +25684,7 @@ function defineModel(definition) {
25556
25684
  getName: () => definition.name
25557
25685
  });
25558
25686
  }
25559
- modelRegistry.set(definition.name, model);
25687
+ registerModel(definition.name, model);
25560
25688
  return model;
25561
25689
  }
25562
25690
  function registerBrowserModels(models) {
@@ -27853,7 +27981,7 @@ function defineSeeder(seederClass) {
27853
27981
  return seederClass;
27854
27982
  }
27855
27983
  // src/index.ts
27856
- var init_src2 = __esm(() => {
27984
+ var init_src2 = __esm(async () => {
27857
27985
  init_model2();
27858
27986
  init_model2();
27859
27987
  init_actions();
@@ -27870,7 +27998,7 @@ var init_src2 = __esm(() => {
27870
27998
  init_migrations();
27871
27999
  init_orm();
27872
28000
  });
27873
- init_src2();
28001
+ await init_src2();
27874
28002
 
27875
28003
  export {
27876
28004
  dbWipe as wipeDatabase,
@@ -27897,6 +28025,7 @@ export {
27897
28025
  resetDatabase,
27898
28026
  resetConnection,
27899
28027
  relationDiagram,
28028
+ registerModel,
27900
28029
  registerBrowserModels,
27901
28030
  queryExplainAll,
27902
28031
  ping,