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.
@@ -3,6 +3,14 @@
3
3
  */
4
4
  // eslint-disable-next-line pickier/no-unused-vars
5
5
  export declare function exportData(tableName: string, options?: ExportOptions): Promise<void>;
6
+ /**
7
+ * Import data into a table
8
+ */
9
+ export declare function importData(tableName: string, filePath: string, options?: ImportOptions): Promise<void>;
10
+ /**
11
+ * Dump database or specific tables to SQL
12
+ */
13
+ export declare function dumpDatabase(options?: DumpOptions): Promise<void>;
6
14
  export declare interface ExportOptions {
7
15
  format?: 'json' | 'csv' | 'sql'
8
16
  output?: string
package/dist/bin/cli.js CHANGED
@@ -11663,9 +11663,6 @@ async function getConfig() {
11663
11663
  return _config;
11664
11664
  }
11665
11665
  function setConfig(userConfig) {
11666
- if (config5 === undefined || config5.dialect === undefined) {
11667
- config5 = { ...defaultConfig4 };
11668
- }
11669
11666
  Object.assign(config5, userConfig);
11670
11667
  if (userConfig.database) {
11671
11668
  config5.database = { ...config5.database, ...userConfig.database };
@@ -12380,6 +12377,7 @@ function createQueryBuilder(state) {
12380
12377
  function applyCondition(expr) {
12381
12378
  if (Array.isArray(expr)) {
12382
12379
  const [col, op, val] = expr;
12380
+ validateIdentifier(col, "where(column)");
12383
12381
  const colName = String(col);
12384
12382
  switch (op) {
12385
12383
  case "in":
@@ -12397,9 +12395,12 @@ function createQueryBuilder(state) {
12397
12395
  case "like":
12398
12396
  return _sql.unsafe(`${colName} LIKE ${getPlaceholder(1)}`, [val]);
12399
12397
  case "is":
12400
- return _sql.unsafe(`${colName} IS ${val}`);
12401
- case "is not":
12402
- 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
+ }
12403
12404
  case "!=":
12404
12405
  return _sql.unsafe(`${colName} <> ${getPlaceholder(1)}`, [val]);
12405
12406
  case "<":
@@ -12407,8 +12408,9 @@ function createQueryBuilder(state) {
12407
12408
  case "<=":
12408
12409
  case ">=":
12409
12410
  case "=":
12410
- default:
12411
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`);
12412
12414
  }
12413
12415
  }
12414
12416
  if ("raw" in expr) {
@@ -12421,6 +12423,7 @@ function createQueryBuilder(state) {
12421
12423
  const allParams = [];
12422
12424
  let paramIndex = 1;
12423
12425
  for (const key of keys) {
12426
+ validateIdentifier(key, "where(object key)");
12424
12427
  const value = expr[key];
12425
12428
  if (Array.isArray(value)) {
12426
12429
  const placeholders = getPlaceholders(value.length, paramIndex);
@@ -12609,17 +12612,17 @@ function createQueryBuilder(state) {
12609
12612
  return;
12610
12613
  const parentTable = String(table);
12611
12614
  const parentPk = meta.primaryKeys[parentTable] ?? "id";
12612
- validateIdentifier(resolved.pivotTable, "wherePivot auto-join (pivot table)");
12613
- validateIdentifier(resolved.fkParent, "wherePivot auto-join (parent FK)");
12614
- validateIdentifier(parentTable, "wherePivot auto-join (parent table)");
12615
- 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)");
12616
12619
  built = sql`${ensureBuilt()} LEFT JOIN ${sql(resolved.pivotTable)} ON ${sql(`${resolved.pivotTable}.${resolved.fkParent}`)} = ${sql(`${parentTable}.${parentPk}`)}`;
12617
12620
  text = `${text} LEFT JOIN ${resolved.pivotTable} ON ${resolved.pivotTable}.${resolved.fkParent} = ${parentTable}.${parentPk}`;
12618
12621
  joinedTables.add(resolved.pivotTable);
12619
12622
  };
12620
12623
  const whereConditions = [];
12621
12624
  const whereParams = [];
12622
- const validateIdentifier = (name, context) => {
12625
+ const validateIdentifier2 = (name, context) => {
12623
12626
  if (!SQL_PATTERNS.IDENTIFIER.test(name)) {
12624
12627
  const contextMsg = context ? ` in ${context}` : "";
12625
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.`);
@@ -12643,18 +12646,56 @@ function createQueryBuilder(state) {
12643
12646
  }
12644
12647
  }
12645
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
+ }
12646
12687
  const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
12647
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12648
- validateIdentifier(targetTable, "relationship subquery (target table)");
12649
- 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)");
12650
12691
  const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
12651
- validateIdentifier(fk, "relationship subquery (foreign key)");
12692
+ validateIdentifier2(fk, "relationship subquery (foreign key)");
12652
12693
  let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk}`;
12653
12694
  if (callback) {
12654
12695
  const subQb = {
12655
12696
  where: (col, op, val) => {
12656
- validateIdentifier(col, "relationship subquery condition");
12657
- 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)}`;
12658
12699
  }
12659
12700
  };
12660
12701
  const condition = callback(subQb);
@@ -12665,16 +12706,16 @@ function createQueryBuilder(state) {
12665
12706
  return subquerySQL;
12666
12707
  };
12667
12708
  const buildBelongsToSubquery = (parentTable, targetTable, pk, callback) => {
12668
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12669
- validateIdentifier(targetTable, "relationship subquery (target table)");
12670
- 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)");
12671
12712
  const fk = `${targetTable.endsWith("s") ? targetTable.slice(0, -1) : targetTable}_id`;
12672
- validateIdentifier(fk, "relationship subquery (foreign key)");
12713
+ validateIdentifier2(fk, "relationship subquery (foreign key)");
12673
12714
  let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${pk} = ${parentTable}.${fk}`;
12674
12715
  if (callback) {
12675
12716
  const subQb = {
12676
12717
  where: (col, op, val) => {
12677
- validateIdentifier(col, "relationship subquery condition");
12718
+ validateIdentifier2(col, "relationship subquery condition");
12678
12719
  return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
12679
12720
  }
12680
12721
  };
@@ -12686,24 +12727,24 @@ function createQueryBuilder(state) {
12686
12727
  return subquerySQL;
12687
12728
  };
12688
12729
  const buildBelongsToManySubquery = (parentTable, targetTable, pk, targetPk, callback, relationKey) => {
12689
- validateIdentifier(parentTable, "relationship subquery (parent table)");
12690
- validateIdentifier(targetTable, "relationship subquery (target table)");
12691
- validateIdentifier(pk, "relationship subquery (primary key)");
12692
- 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)");
12693
12734
  const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
12694
12735
  const a = singularize(parentTable);
12695
12736
  const b = singularize(targetTable);
12696
12737
  const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
12697
12738
  const fkA = resolved?.fkParent ?? `${a}_id`;
12698
12739
  const fkB = resolved?.fkRelated ?? `${b}_id`;
12699
- validateIdentifier(pivot, "relationship subquery (pivot table)");
12700
- validateIdentifier(fkA, "relationship subquery (foreign key A)");
12701
- 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)");
12702
12743
  let subquerySQL = `SELECT 1 FROM ${pivot} JOIN ${targetTable} ON ${targetTable}.${targetPk} = ${pivot}.${fkB} WHERE ${pivot}.${fkA} = ${parentTable}.${pk}`;
12703
12744
  if (callback) {
12704
12745
  const subQb = {
12705
12746
  where: (col, op, val) => {
12706
- validateIdentifier(col, "relationship subquery condition");
12747
+ validateIdentifier2(col, "relationship subquery condition");
12707
12748
  return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
12708
12749
  }
12709
12750
  };
@@ -12715,24 +12756,24 @@ function createQueryBuilder(state) {
12715
12756
  return subquerySQL;
12716
12757
  };
12717
12758
  const buildHasCountSubquery = (parentTable, targetTable, pk) => {
12718
- validateIdentifier(parentTable, "withCount (parent table)");
12719
- validateIdentifier(targetTable, "withCount (target table)");
12720
- validateIdentifier(pk, "withCount (primary key)");
12759
+ validateIdentifier2(parentTable, "withCount (parent table)");
12760
+ validateIdentifier2(targetTable, "withCount (target table)");
12761
+ validateIdentifier2(pk, "withCount (primary key)");
12721
12762
  const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
12722
- validateIdentifier(fk, "withCount (foreign key)");
12763
+ validateIdentifier2(fk, "withCount (foreign key)");
12723
12764
  return `(SELECT COUNT(*) FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk})`;
12724
12765
  };
12725
12766
  const buildBelongsToManyCountSubquery = (parentTable, targetTable, pk, relationKey) => {
12726
- validateIdentifier(parentTable, "withCount (parent table)");
12727
- validateIdentifier(targetTable, "withCount (target table)");
12728
- validateIdentifier(pk, "withCount (primary key)");
12767
+ validateIdentifier2(parentTable, "withCount (parent table)");
12768
+ validateIdentifier2(targetTable, "withCount (target table)");
12769
+ validateIdentifier2(pk, "withCount (primary key)");
12729
12770
  const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
12730
12771
  const a = singularize(parentTable);
12731
12772
  const b = singularize(targetTable);
12732
12773
  const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
12733
12774
  const fkA = resolved?.fkParent ?? `${a}_id`;
12734
- validateIdentifier(pivot, "withCount (pivot table)");
12735
- validateIdentifier(fkA, "withCount (foreign key)");
12775
+ validateIdentifier2(pivot, "withCount (pivot table)");
12776
+ validateIdentifier2(fkA, "withCount (foreign key)");
12736
12777
  return `(SELECT COUNT(*) FROM ${pivot} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
12737
12778
  };
12738
12779
  const applyPivotColumnsToQuery = () => {
@@ -12744,7 +12785,7 @@ function createQueryBuilder(state) {
12744
12785
  if (!resolved)
12745
12786
  continue;
12746
12787
  for (const col of columns2) {
12747
- validateIdentifier(col, "withPivot");
12788
+ validateIdentifier2(col, "withPivot");
12748
12789
  }
12749
12790
  const pivotColumnsStr = columns2.map((col) => `${resolved.pivotTable}.${col} AS pivot_${col}`);
12750
12791
  allPivotColumns.push(...pivotColumnsStr);
@@ -13294,8 +13335,8 @@ function createQueryBuilder(state) {
13294
13335
  if (!resolved) {
13295
13336
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13296
13337
  }
13297
- validateIdentifier(resolved.pivotTable, "wherePivot (pivot table)");
13298
- validateIdentifier(column, "wherePivot (column)");
13338
+ validateIdentifier2(resolved.pivotTable, "wherePivot (pivot table)");
13339
+ validateIdentifier2(column, "wherePivot (column)");
13299
13340
  ensurePivotJoined(resolved);
13300
13341
  const op = value === undefined ? "=" : String(opOrValue);
13301
13342
  const val = value === undefined ? opOrValue : value;
@@ -13317,8 +13358,8 @@ function createQueryBuilder(state) {
13317
13358
  if (!resolved) {
13318
13359
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13319
13360
  }
13320
- validateIdentifier(resolved.pivotTable, "wherePivotIn (pivot table)");
13321
- validateIdentifier(column, "wherePivotIn (column)");
13361
+ validateIdentifier2(resolved.pivotTable, "wherePivotIn (pivot table)");
13362
+ validateIdentifier2(column, "wherePivotIn (column)");
13322
13363
  ensurePivotJoined(resolved);
13323
13364
  const placeholders = getPlaceholders(values.length, whereParams.length + 1);
13324
13365
  const clause = `${resolved.pivotTable}.${column} IN (${placeholders})`;
@@ -13338,8 +13379,8 @@ function createQueryBuilder(state) {
13338
13379
  if (!resolved) {
13339
13380
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13340
13381
  }
13341
- validateIdentifier(resolved.pivotTable, "wherePivotNotIn (pivot table)");
13342
- validateIdentifier(column, "wherePivotNotIn (column)");
13382
+ validateIdentifier2(resolved.pivotTable, "wherePivotNotIn (pivot table)");
13383
+ validateIdentifier2(column, "wherePivotNotIn (column)");
13343
13384
  ensurePivotJoined(resolved);
13344
13385
  const placeholders = getPlaceholders(values.length, whereParams.length + 1);
13345
13386
  const clause = `${resolved.pivotTable}.${column} NOT IN (${placeholders})`;
@@ -13359,8 +13400,8 @@ function createQueryBuilder(state) {
13359
13400
  if (!resolved) {
13360
13401
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13361
13402
  }
13362
- validateIdentifier(resolved.pivotTable, "wherePivotNull (pivot table)");
13363
- validateIdentifier(column, "wherePivotNull (column)");
13403
+ validateIdentifier2(resolved.pivotTable, "wherePivotNull (pivot table)");
13404
+ validateIdentifier2(column, "wherePivotNull (column)");
13364
13405
  ensurePivotJoined(resolved);
13365
13406
  const clause = `${resolved.pivotTable}.${column} IS NULL`;
13366
13407
  whereConditions.push(clause);
@@ -13378,8 +13419,8 @@ function createQueryBuilder(state) {
13378
13419
  if (!resolved) {
13379
13420
  throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
13380
13421
  }
13381
- validateIdentifier(resolved.pivotTable, "wherePivotNotNull (pivot table)");
13382
- validateIdentifier(column, "wherePivotNotNull (column)");
13422
+ validateIdentifier2(resolved.pivotTable, "wherePivotNotNull (pivot table)");
13423
+ validateIdentifier2(column, "wherePivotNotNull (column)");
13383
13424
  ensurePivotJoined(resolved);
13384
13425
  const clause = `${resolved.pivotTable}.${column} IS NOT NULL`;
13385
13426
  whereConditions.push(clause);
@@ -13612,10 +13653,14 @@ function createQueryBuilder(state) {
13612
13653
  return this;
13613
13654
  },
13614
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
+ })();
13615
13660
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13616
13661
  const idx = whereParams.length + 1;
13617
13662
  text += ` ${keyword} ${column} ${op} ${getPlaceholder(idx)}`;
13618
- whereParams.push(String(date));
13663
+ whereParams.push(dateString);
13619
13664
  built = null;
13620
13665
  return this;
13621
13666
  },
@@ -13626,12 +13671,16 @@ function createQueryBuilder(state) {
13626
13671
  return this;
13627
13672
  },
13628
13673
  whereColumn(left, op, right) {
13674
+ validateIdentifier2(left, "whereColumn(left)");
13675
+ validateIdentifier2(right, "whereColumn(right)");
13629
13676
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
13630
13677
  text += ` ${keyword} ${left} ${op} ${right}`;
13631
13678
  built = null;
13632
13679
  return this;
13633
13680
  },
13634
13681
  orWhereColumn(left, op, right) {
13682
+ validateIdentifier2(left, "orWhereColumn(left)");
13683
+ validateIdentifier2(right, "orWhereColumn(right)");
13635
13684
  text += ` OR ${left} ${op} ${right}`;
13636
13685
  built = null;
13637
13686
  return this;
@@ -13851,11 +13900,15 @@ function createQueryBuilder(state) {
13851
13900
  return this;
13852
13901
  },
13853
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}`);
13854
13905
  text = SQL_PATTERNS.LIMIT.test(text) ? text.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`) : `${text} LIMIT ${n}`;
13855
13906
  built = null;
13856
13907
  return this;
13857
13908
  },
13858
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}`);
13859
13912
  text = SQL_PATTERNS.OFFSET.test(text) ? text.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`) : `${text} OFFSET ${n}`;
13860
13913
  built = null;
13861
13914
  return this;
@@ -14128,19 +14181,25 @@ function createQueryBuilder(state) {
14128
14181
  includeTrashed = true;
14129
14182
  onlyTrashed = true;
14130
14183
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14131
- if (text.includes("WHERE")) {
14132
- text = text.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
14133
- } else {
14134
- text = `${text} WHERE ${table}.${softDeleteColumn} IS NOT NULL`;
14135
- }
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);
14136
14201
  const currentSql = String(ensureBuilt());
14137
- if (currentSql.includes("WHERE")) {
14138
- const newSql = currentSql.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
14139
- built = sql([newSql]);
14140
- } else {
14141
- const newSql = `${currentSql} WHERE ${table}.${softDeleteColumn} IS NOT NULL`;
14142
- built = sql([newSql]);
14143
- }
14202
+ built = sql([splice(currentSql, predicate)]);
14144
14203
  return this;
14145
14204
  },
14146
14205
  scope(name, value) {
@@ -14314,7 +14373,15 @@ function createQueryBuilder(state) {
14314
14373
  },
14315
14374
  async count() {
14316
14375
  const fromIdx = text.indexOf(" FROM ");
14317
- 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
+ }
14318
14385
  const cHooks = config5.hooks;
14319
14386
  const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan);
14320
14387
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
@@ -14738,7 +14805,7 @@ function createQueryBuilder(state) {
14738
14805
  let sqlText = "";
14739
14806
  const params = [];
14740
14807
  const isPostgres = config5.dialect === "postgres";
14741
- 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, '""')}"`;
14742
14809
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
14743
14810
  return {
14744
14811
  values(data) {
@@ -14871,11 +14938,11 @@ function createQueryBuilder(state) {
14871
14938
  updateTable(table) {
14872
14939
  let built;
14873
14940
  const params = [];
14874
- const quoteId = (id) => {
14875
- if (config5.dialect === "mysql") {
14876
- return `\`${id}\``;
14877
- }
14878
- 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, '""')}"`;
14879
14946
  };
14880
14947
  let sqlText = `UPDATE ${quoteId(String(table))}`;
14881
14948
  return {
@@ -14979,11 +15046,11 @@ function createQueryBuilder(state) {
14979
15046
  };
14980
15047
  },
14981
15048
  deleteFrom(table) {
14982
- const quoteId = (id) => {
14983
- if (config5.dialect === "mysql") {
14984
- return `\`${id}\``;
14985
- }
14986
- 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, '""')}"`;
14987
15054
  };
14988
15055
  const quotedTable = quoteId(String(table));
14989
15056
  let sqlText = `DELETE FROM ${quotedTable}`;
@@ -15267,6 +15334,10 @@ function createQueryBuilder(state) {
15267
15334
  };
15268
15335
  },
15269
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
+ }
15270
15341
  const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} ON CONFLICT DO NOTHING`;
15271
15342
  return built.execute();
15272
15343
  },
@@ -15302,7 +15373,15 @@ function createQueryBuilder(state) {
15302
15373
  async upsert(table, rows, conflictColumns, mergeColumns) {
15303
15374
  const targetCols = conflictColumns.map((c) => String(c));
15304
15375
  const setCols = (mergeColumns ?? []).map((c) => String(c));
15305
- 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)}`;
15306
15385
  return built.execute();
15307
15386
  },
15308
15387
  async save(table, values) {
@@ -24067,6 +24146,14 @@ var init_src = __esm(async () => {
24067
24146
 
24068
24147
  // src/orm.ts
24069
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
+ }
24070
24157
  function getModelFromRegistry(name) {
24071
24158
  if (!_getModel) {
24072
24159
  try {
@@ -24617,14 +24704,21 @@ class BelongsToManyRelationBuilder {
24617
24704
  return this;
24618
24705
  }
24619
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}'`);
24620
24710
  this._orderBy.push(`${column} ${direction.toUpperCase()}`);
24621
24711
  return this;
24622
24712
  }
24623
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}`);
24624
24716
  this._limit = n2;
24625
24717
  return this;
24626
24718
  }
24627
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}`);
24628
24722
  this._offset = n2;
24629
24723
  return this;
24630
24724
  }
@@ -24851,18 +24945,22 @@ class ModelQueryBuilder {
24851
24945
  return this;
24852
24946
  }
24853
24947
  whereNull(column) {
24948
+ assertValidIdentifier(column, "whereNull(column)");
24854
24949
  this._wheres.push({ column, operator: "=", value: null, boolean: "and" });
24855
24950
  return this;
24856
24951
  }
24857
24952
  orWhereNull(column) {
24953
+ assertValidIdentifier(column, "orWhereNull(column)");
24858
24954
  this._wheres.push({ column, operator: "=", value: null, boolean: "or" });
24859
24955
  return this;
24860
24956
  }
24861
24957
  whereNotNull(column) {
24958
+ assertValidIdentifier(column, "whereNotNull(column)");
24862
24959
  this._wheres.push({ column, operator: "!=", value: null, boolean: "and" });
24863
24960
  return this;
24864
24961
  }
24865
24962
  orWhereNotNull(column) {
24963
+ assertValidIdentifier(column, "orWhereNotNull(column)");
24866
24964
  this._wheres.push({ column, operator: "!=", value: null, boolean: "or" });
24867
24965
  return this;
24868
24966
  }
@@ -24909,13 +25007,19 @@ class ModelQueryBuilder {
24909
25007
  return this;
24910
25008
  }
24911
25009
  whereBetween(column, range) {
25010
+ assertValidIdentifier(column, "whereBetween(column)");
24912
25011
  this._wheres.push({ column, operator: ">=", value: range[0], boolean: "and" });
24913
25012
  this._wheres.push({ column, operator: "<=", value: range[1], boolean: "and" });
24914
25013
  return this;
24915
25014
  }
24916
25015
  whereNotBetween(column, range) {
24917
- this._wheres.push({ column, operator: "<", value: range[0], boolean: "and" });
24918
- 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
+ });
24919
25023
  return this;
24920
25024
  }
24921
25025
  when(condition, callback) {
@@ -24925,6 +25029,9 @@ class ModelQueryBuilder {
24925
25029
  return this;
24926
25030
  }
24927
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}'`);
24928
25035
  this._orderBy.push({ column, direction });
24929
25036
  return this;
24930
25037
  }
@@ -25169,6 +25276,7 @@ class ModelQueryBuilder {
25169
25276
  return results[0];
25170
25277
  }
25171
25278
  increment(column, amount = 1) {
25279
+ assertValidIdentifier(column, "increment(column)");
25172
25280
  const db = getDatabase();
25173
25281
  const params = [amount];
25174
25282
  let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
@@ -25225,6 +25333,7 @@ class ModelQueryBuilder {
25225
25333
  };
25226
25334
  }
25227
25335
  pluck(column) {
25336
+ assertValidIdentifier(column, "pluck(column)");
25228
25337
  const db = getDatabase();
25229
25338
  const params = [];
25230
25339
  let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
@@ -25242,6 +25351,7 @@ class ModelQueryBuilder {
25242
25351
  return rows.map((r2) => r2[column]);
25243
25352
  }
25244
25353
  aggregate(fn, column) {
25354
+ assertValidIdentifier(column, `${fn}(column)`);
25245
25355
  const db = getDatabase();
25246
25356
  const params = [];
25247
25357
  let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
@@ -25518,8 +25628,9 @@ async function seedModel(definition, count, faker) {
25518
25628
  db.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
25519
25629
  }
25520
25630
  }
25521
- var _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
25631
+ var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
25522
25632
  var init_orm = __esm(() => {
25633
+ SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
25523
25634
  snakeCaseCache = new Map;
25524
25635
  tableNameCache = new Map;
25525
25636
  relationCache = new Map;
@@ -25529,6 +25640,7 @@ var init_orm = __esm(() => {
25529
25640
  // src/model.ts
25530
25641
  var exports_model = {};
25531
25642
  __export(exports_model, {
25643
+ registerModel: () => registerModel,
25532
25644
  registerBrowserModels: () => registerBrowserModels,
25533
25645
  hasModel: () => hasModel,
25534
25646
  getModelRegistry: () => getModelRegistry,
@@ -25552,6 +25664,10 @@ function hasModel(name) {
25552
25664
  function clearModelRegistry() {
25553
25665
  modelRegistry.clear();
25554
25666
  }
25667
+ function registerModel(name, model) {
25668
+ modelRegistry.set(name, model);
25669
+ return model;
25670
+ }
25555
25671
  function isClientSide() {
25556
25672
  return typeof window !== "undefined" && typeof document !== "undefined";
25557
25673
  }
@@ -25568,7 +25684,7 @@ function defineModel(definition) {
25568
25684
  getName: () => definition.name
25569
25685
  });
25570
25686
  }
25571
- modelRegistry.set(definition.name, model);
25687
+ registerModel(definition.name, model);
25572
25688
  return model;
25573
25689
  }
25574
25690
  function registerBrowserModels(models) {
@@ -29921,7 +30037,7 @@ function getPrefix() {
29921
30037
  }
29922
30038
  var prefix = getPrefix();
29923
30039
  // package.json
29924
- var version2 = "0.1.20";
30040
+ var version2 = "0.1.23";
29925
30041
 
29926
30042
  // bin/cli.ts
29927
30043
  init_actions();