bun-query-builder 0.1.31 → 0.1.33

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
@@ -12344,6 +12344,14 @@ function* iterateAllPivots(meta, options = {}) {
12344
12344
  function isRawExpression(expr) {
12345
12345
  return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
12346
12346
  }
12347
+ function raw(strings, ...values) {
12348
+ if (typeof strings === "string")
12349
+ return { raw: strings };
12350
+ let out = strings[0];
12351
+ for (let i = 0;i < values.length; i++)
12352
+ out += formatSubqueryValue(values[i]) + strings[i + 1];
12353
+ return { raw: out };
12354
+ }
12347
12355
  function quoteInsertIdent(id) {
12348
12356
  return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
12349
12357
  }
@@ -12412,13 +12420,31 @@ function validateQualifiedIdentifier(value, context) {
12412
12420
  throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
12413
12421
  }
12414
12422
  }
12415
- function assertSqlFragment(fragment, context) {
12416
- if (fragment === null || fragment === undefined) {
12417
- throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
12418
- }
12423
+ function renderRawFragment(fragment, context) {
12419
12424
  if (typeof fragment === "string") {
12420
12425
  warnOnceBareSqlFragment(context);
12426
+ return fragment;
12421
12427
  }
12428
+ if (fragment === null || fragment === undefined)
12429
+ throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
12430
+ if (isRawExpression(fragment))
12431
+ return fragment.raw;
12432
+ if (typeof fragment === "object") {
12433
+ const f = fragment;
12434
+ if (typeof f.raw === "string")
12435
+ return f.raw;
12436
+ if (typeof f.raw === "function") {
12437
+ const r = f.raw();
12438
+ if (typeof r === "string")
12439
+ return r;
12440
+ }
12441
+ if (typeof f.sql === "string")
12442
+ return f.sql;
12443
+ const s = String(fragment);
12444
+ if (s !== "[object Object]" && s !== "[object Promise]")
12445
+ return s;
12446
+ }
12447
+ throw new TypeError(`[query-builder] ${context}: cannot render this value as a SQL fragment. ` + `A Bun \`sql\`...\`\` query object cannot be converted to SQL text \u2014 pass a ` + `string, or use the exported \`raw\` helper: raw\`count(*) as c\` / raw('age > 18').`);
12422
12448
  }
12423
12449
  function warnOnceBareSqlFragment(context) {
12424
12450
  if (warnedSqlFragmentContexts.has(context))
@@ -12809,7 +12835,7 @@ function createQueryBuilder(state) {
12809
12835
  };
12810
12836
  const addWhereText = (prefix, clause) => {
12811
12837
  const hasWhere = SQL_PATTERNS.WHERE.test(text);
12812
- const p = hasWhere ? prefix : "WHERE";
12838
+ const p = !hasWhere ? "WHERE" : prefix === "WHERE" ? "AND" : prefix;
12813
12839
  text = `${text} ${p} ${clause}`;
12814
12840
  };
12815
12841
  const appendSetOp = (op, other) => {
@@ -13093,12 +13119,12 @@ function createQueryBuilder(state) {
13093
13119
  return this;
13094
13120
  },
13095
13121
  selectRaw(fragment) {
13096
- assertSqlFragment(fragment, "selectRaw(fragment)");
13122
+ const frag = renderRawFragment(fragment, "selectRaw(fragment)");
13097
13123
  const fromIdx = text.indexOf(" FROM ");
13098
13124
  if (fromIdx !== -1) {
13099
- text = `${text.substring(0, fromIdx)}, ${String(fragment)}${text.substring(fromIdx)}`;
13125
+ text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
13100
13126
  } else {
13101
- text += `, ${String(fragment)}`;
13127
+ text += `, ${frag}`;
13102
13128
  }
13103
13129
  built = null;
13104
13130
  return this;
@@ -13211,12 +13237,15 @@ function createQueryBuilder(state) {
13211
13237
  if (cols.length === 0)
13212
13238
  return this;
13213
13239
  const rendered = cols.map(renderSelectColumn);
13240
+ const distinctMatch = /^SELECT\s+(DISTINCT(?:\s+ON\s+\([^)]*\))?\s+)/i.exec(text);
13241
+ const distinctPrefix = distinctMatch ? distinctMatch[1] : "";
13214
13242
  const fromIndex = text.indexOf(" FROM ");
13215
13243
  if (fromIndex !== -1) {
13216
- text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
13244
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
13217
13245
  } else {
13218
- text = `SELECT ${rendered.join(", ")} FROM ${table}`;
13246
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
13219
13247
  }
13248
+ built = null;
13220
13249
  return this;
13221
13250
  },
13222
13251
  addSelect(...columns2) {
@@ -13964,87 +13993,71 @@ function createQueryBuilder(state) {
13964
13993
  return this;
13965
13994
  },
13966
13995
  whereLike(column, pattern, caseSensitive = false) {
13967
- const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13968
- built = sql`${ensureBuilt()} WHERE ${expr}`;
13969
13996
  const ph = getPlaceholder(whereParams.length + 1);
13970
13997
  addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13971
13998
  whereParams.push(pattern);
13999
+ built = null;
13972
14000
  return this;
13973
14001
  },
13974
14002
  whereILike(column, pattern) {
13975
14003
  const ph = getPlaceholder(whereParams.length + 1);
13976
- if (config5.dialect === "postgres") {
13977
- built = sql`${ensureBuilt()} WHERE ${sql(String(column))} ILIKE ${pattern}`;
14004
+ if (config5.dialect === "postgres")
13978
14005
  addWhereText("WHERE", `${String(column)} ILIKE ${ph}`);
13979
- } else {
13980
- const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13981
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14006
+ else
13982
14007
  addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
13983
- }
13984
14008
  whereParams.push(pattern);
14009
+ built = null;
13985
14010
  return this;
13986
14011
  },
13987
14012
  orWhereLike(column, pattern, caseSensitive = false) {
13988
- const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13989
- built = sql`${ensureBuilt()} OR ${expr}`;
13990
14013
  const ph = getPlaceholder(whereParams.length + 1);
13991
14014
  addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13992
14015
  whereParams.push(pattern);
14016
+ built = null;
13993
14017
  return this;
13994
14018
  },
13995
14019
  orWhereILike(column, pattern) {
13996
14020
  const ph = getPlaceholder(whereParams.length + 1);
13997
- if (config5.dialect === "postgres") {
13998
- built = sql`${ensureBuilt()} OR ${sql(String(column))} ILIKE ${pattern}`;
14021
+ if (config5.dialect === "postgres")
13999
14022
  addWhereText("OR", `${String(column)} ILIKE ${ph}`);
14000
- } else {
14001
- const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
14002
- built = sql`${ensureBuilt()} OR ${expr}`;
14023
+ else
14003
14024
  addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
14004
- }
14005
14025
  whereParams.push(pattern);
14026
+ built = null;
14006
14027
  return this;
14007
14028
  },
14008
14029
  whereNotLike(column, pattern, caseSensitive = false) {
14009
- const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14010
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14011
14030
  const ph = getPlaceholder(whereParams.length + 1);
14012
14031
  addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
14013
14032
  whereParams.push(pattern);
14033
+ built = null;
14014
14034
  return this;
14015
14035
  },
14016
14036
  whereNotILike(column, pattern) {
14017
14037
  const ph = getPlaceholder(whereParams.length + 1);
14018
- if (config5.dialect === "postgres") {
14019
- built = sql`${ensureBuilt()} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
14038
+ if (config5.dialect === "postgres")
14020
14039
  addWhereText("WHERE", `${String(column)} NOT ILIKE ${ph}`);
14021
- } else {
14022
- const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14023
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14040
+ else
14024
14041
  addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
14025
- }
14026
14042
  whereParams.push(pattern);
14043
+ built = null;
14027
14044
  return this;
14028
14045
  },
14029
14046
  orWhereNotLike(column, pattern, caseSensitive = false) {
14030
- const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14031
- built = sql`${ensureBuilt()} OR ${expr}`;
14032
14047
  const ph = getPlaceholder(whereParams.length + 1);
14033
14048
  addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
14034
14049
  whereParams.push(pattern);
14050
+ built = null;
14035
14051
  return this;
14036
14052
  },
14037
14053
  orWhereNotILike(column, pattern) {
14038
14054
  const ph = getPlaceholder(whereParams.length + 1);
14039
- if (config5.dialect === "postgres") {
14040
- built = sql`${ensureBuilt()} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
14055
+ if (config5.dialect === "postgres")
14041
14056
  addWhereText("OR", `${String(column)} NOT ILIKE ${ph}`);
14042
- } else {
14043
- const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14044
- built = sql`${ensureBuilt()} OR ${expr}`;
14057
+ else
14045
14058
  addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
14046
- }
14047
14059
  whereParams.push(pattern);
14060
+ built = null;
14048
14061
  return this;
14049
14062
  },
14050
14063
  whereAny(cols, op, value) {
@@ -14105,9 +14118,9 @@ function createQueryBuilder(state) {
14105
14118
  return this;
14106
14119
  },
14107
14120
  whereRaw(fragment) {
14108
- assertSqlFragment(fragment, "whereRaw(fragment)");
14121
+ const frag = renderRawFragment(fragment, "whereRaw(fragment)");
14109
14122
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
14110
- text += ` ${keyword} ${String(fragment)}`;
14123
+ text += ` ${keyword} ${frag}`;
14111
14124
  built = null;
14112
14125
  return this;
14113
14126
  },
@@ -14473,8 +14486,8 @@ function createQueryBuilder(state) {
14473
14486
  return this;
14474
14487
  },
14475
14488
  groupByRaw(fragment) {
14476
- assertSqlFragment(fragment, "groupByRaw(fragment)");
14477
- text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} GROUP BY ${String(fragment)}`;
14489
+ const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
14490
+ text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
14478
14491
  built = null;
14479
14492
  return this;
14480
14493
  },
@@ -14506,15 +14519,15 @@ function createQueryBuilder(state) {
14506
14519
  return this;
14507
14520
  },
14508
14521
  havingRaw(fragment) {
14509
- assertSqlFragment(fragment, "havingRaw(fragment)");
14522
+ const frag = renderRawFragment(fragment, "havingRaw(fragment)");
14510
14523
  const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14511
- text += ` ${kw} ${String(fragment)}`;
14524
+ text += ` ${kw} ${frag}`;
14512
14525
  built = null;
14513
14526
  return this;
14514
14527
  },
14515
14528
  orderByRaw(fragment) {
14516
- assertSqlFragment(fragment, "orderByRaw(fragment)");
14517
- text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} ORDER BY ${String(fragment)}`;
14529
+ const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
14530
+ text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
14518
14531
  built = null;
14519
14532
  return this;
14520
14533
  },
@@ -14644,19 +14657,21 @@ function createQueryBuilder(state) {
14644
14657
  q = sql`${q} ORDER BY ${sql(String(column))} ${direction === "asc" ? sql`ASC` : sql`DESC`} LIMIT ${perPage + 1}`;
14645
14658
  }
14646
14659
  const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
14647
- const next = rows.length > perPage ? Array.isArray(column) ? column.map((c) => rows[perPage]?.[c]) : rows[perPage]?.[column] : null;
14660
+ const hasMore = rows.length > perPage;
14648
14661
  const data = rows.slice(0, perPage);
14662
+ const lastRow = data[data.length - 1];
14663
+ const next = hasMore && lastRow ? Array.isArray(column) ? column.map((c) => lastRow[c]) : lastRow[column] : null;
14649
14664
  const prevCursor = data.length ? Array.isArray(column) ? column.map((c) => data[0]?.[c]) : data[0]?.[column] : null;
14650
14665
  return { data, meta: { perPage, nextCursor: next ?? null, prevCursor } };
14651
14666
  },
14652
14667
  async chunk(size, handler) {
14653
14668
  let page = 1;
14654
14669
  while (true) {
14655
- const { data } = await this.paginate(size, page);
14670
+ const { data, meta: meta2 } = await this.paginate(size, page);
14656
14671
  if (data.length === 0)
14657
14672
  break;
14658
14673
  await handler(data);
14659
- if (data.length < size)
14674
+ if (page >= meta2.lastPage)
14660
14675
  break;
14661
14676
  page += 1;
14662
14677
  }
@@ -14697,20 +14712,20 @@ function createQueryBuilder(state) {
14697
14712
  includeTrashed = true;
14698
14713
  onlyTrashed = true;
14699
14714
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14700
- const splice = (raw, predicate2) => {
14701
- const upper = raw.toUpperCase();
14715
+ const splice = (raw2, predicate2) => {
14716
+ const upper = raw2.toUpperCase();
14702
14717
  let depth = 0;
14703
- for (let i = 0;i < raw.length; i++) {
14704
- const c = raw[i];
14718
+ for (let i = 0;i < raw2.length; i++) {
14719
+ const c = raw2[i];
14705
14720
  if (c === "(")
14706
14721
  depth++;
14707
14722
  else if (c === ")")
14708
14723
  depth--;
14709
- else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
14710
- return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
14724
+ else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
14725
+ return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
14711
14726
  }
14712
14727
  }
14713
- return `${raw} WHERE ${predicate2}`;
14728
+ return `${raw2} WHERE ${predicate2}`;
14714
14729
  };
14715
14730
  const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
14716
14731
  text = splice(text, predicate);
@@ -15014,13 +15029,13 @@ function createQueryBuilder(state) {
15014
15029
  const cacheKey = `${String(table)}|${prop}`;
15015
15030
  let chosen = dynamicWhereColumnCache.get(cacheKey);
15016
15031
  if (chosen === undefined) {
15017
- const raw = prop.replace(/^(?:or|and)?where/i, "");
15018
- if (!raw) {
15032
+ const raw2 = prop.replace(/^(?:or|and)?where/i, "");
15033
+ if (!raw2) {
15019
15034
  dynamicWhereColumnCache.set(cacheKey, "");
15020
15035
  chosen = "";
15021
15036
  } else {
15022
- const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
15023
- const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15037
+ const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
15038
+ const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15024
15039
  const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
15025
15040
  chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
15026
15041
  dynamicWhereColumnCache.set(cacheKey, chosen);
@@ -15410,6 +15425,10 @@ function createQueryBuilder(state) {
15410
15425
  returning(...cols) {
15411
15426
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15412
15427
  const q = _sql.unsafe(returningSql, params);
15428
+ const runFirst = async () => {
15429
+ const rows = await runWithHooks(q, "insert");
15430
+ return Array.isArray(rows) ? rows[0] : rows;
15431
+ };
15413
15432
  return {
15414
15433
  where: () => this,
15415
15434
  andWhere: () => this,
@@ -15418,7 +15437,22 @@ function createQueryBuilder(state) {
15418
15437
  limit: () => this,
15419
15438
  offset: () => this,
15420
15439
  toSQL: () => makeExecutableQuery(q, returningSql),
15421
- execute: () => runWithHooks(q, "insert")
15440
+ execute: () => runWithHooks(q, "insert"),
15441
+ get: () => runWithHooks(q, "insert"),
15442
+ first: runFirst,
15443
+ executeTakeFirst: runFirst,
15444
+ async firstOrFail() {
15445
+ const row = await runFirst();
15446
+ if (!row)
15447
+ throw new Error("Insert with RETURNING returned no rows");
15448
+ return row;
15449
+ },
15450
+ async executeTakeFirstOrThrow() {
15451
+ const row = await runFirst();
15452
+ if (!row)
15453
+ throw new Error("Insert with RETURNING returned no rows");
15454
+ return row;
15455
+ }
15422
15456
  };
15423
15457
  },
15424
15458
  toSQL() {
@@ -15457,19 +15491,27 @@ function createQueryBuilder(state) {
15457
15491
  returningAll() {
15458
15492
  const returningSql = `${sqlText} RETURNING *`;
15459
15493
  const q = _sql.unsafe(returningSql, params);
15494
+ const runFirst = async () => {
15495
+ const result = await runWithHooks(q, "insert");
15496
+ return Array.isArray(result) ? result[0] : result;
15497
+ };
15460
15498
  return {
15461
15499
  toSQL: () => makeExecutableQuery(q, returningSql),
15462
15500
  execute: () => runWithHooks(q, "insert"),
15463
- async executeTakeFirst() {
15464
- const result = await runWithHooks(q, "insert");
15465
- return Array.isArray(result) ? result[0] : result;
15501
+ get: () => runWithHooks(q, "insert"),
15502
+ first: runFirst,
15503
+ executeTakeFirst: runFirst,
15504
+ async firstOrFail() {
15505
+ const row = await runFirst();
15506
+ if (!row)
15507
+ throw new Error("Insert with RETURNING returned no rows");
15508
+ return row;
15466
15509
  },
15467
15510
  async executeTakeFirstOrThrow() {
15468
- const result = await runWithHooks(q, "insert");
15469
- const first = Array.isArray(result) ? result[0] : result;
15470
- if (!first)
15471
- throw new Error("Insert with RETURNING failed");
15472
- return first;
15511
+ const row = await runFirst();
15512
+ if (!row)
15513
+ throw new Error("Insert with RETURNING returned no rows");
15514
+ return row;
15473
15515
  }
15474
15516
  };
15475
15517
  }
@@ -15533,6 +15575,10 @@ function createQueryBuilder(state) {
15533
15575
  returning(...cols) {
15534
15576
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15535
15577
  const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
15578
+ const runFirst = async () => {
15579
+ const rows = await runWithHooks(q, "update");
15580
+ return Array.isArray(rows) ? rows[0] : rows;
15581
+ };
15536
15582
  const obj = {
15537
15583
  where: () => obj,
15538
15584
  andWhere: () => obj,
@@ -15541,7 +15587,22 @@ function createQueryBuilder(state) {
15541
15587
  limit: () => obj,
15542
15588
  offset: () => obj,
15543
15589
  toSQL: () => makeExecutableQuery(q, retText),
15544
- execute: () => runWithHooks(q, "update")
15590
+ execute: () => runWithHooks(q, "update"),
15591
+ get: () => runWithHooks(q, "update"),
15592
+ first: runFirst,
15593
+ executeTakeFirst: runFirst,
15594
+ async firstOrFail() {
15595
+ const row = await runFirst();
15596
+ if (!row)
15597
+ throw new Error("Update with RETURNING returned no rows");
15598
+ return row;
15599
+ },
15600
+ async executeTakeFirstOrThrow() {
15601
+ const row = await runFirst();
15602
+ if (!row)
15603
+ throw new Error("Update with RETURNING returned no rows");
15604
+ return row;
15605
+ }
15545
15606
  };
15546
15607
  return obj;
15547
15608
  },
@@ -15640,6 +15701,10 @@ function createQueryBuilder(state) {
15640
15701
  returning(...cols) {
15641
15702
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15642
15703
  const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
15704
+ const runFirst = async () => {
15705
+ const rows = await runWithHooks(q, "delete");
15706
+ return Array.isArray(rows) ? rows[0] : rows;
15707
+ };
15643
15708
  const obj = {
15644
15709
  where: () => obj,
15645
15710
  andWhere: () => obj,
@@ -15648,7 +15713,22 @@ function createQueryBuilder(state) {
15648
15713
  limit: () => obj,
15649
15714
  offset: () => obj,
15650
15715
  toSQL: () => makeExecutableQuery(q, retText),
15651
- execute: () => runWithHooks(q, "delete")
15716
+ execute: () => runWithHooks(q, "delete"),
15717
+ get: () => runWithHooks(q, "delete"),
15718
+ first: runFirst,
15719
+ executeTakeFirst: runFirst,
15720
+ async firstOrFail() {
15721
+ const row = await runFirst();
15722
+ if (!row)
15723
+ throw new Error("Delete with RETURNING returned no rows");
15724
+ return row;
15725
+ },
15726
+ async executeTakeFirstOrThrow() {
15727
+ const row = await runFirst();
15728
+ if (!row)
15729
+ throw new Error("Delete with RETURNING returned no rows");
15730
+ return row;
15731
+ }
15652
15732
  };
15653
15733
  return obj;
15654
15734
  },
@@ -18000,8 +18080,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
18000
18080
  return;
18001
18081
  }
18002
18082
  try {
18003
- const raw = readFileSync3(snapshotPath, "utf8");
18004
- const parsed = JSON.parse(raw);
18083
+ const raw2 = readFileSync3(snapshotPath, "utf8");
18084
+ const parsed = JSON.parse(raw2);
18005
18085
  if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
18006
18086
  return parsed.plan;
18007
18087
  }
@@ -18060,8 +18140,8 @@ async function generateMigration(dir, opts = {}) {
18060
18140
  const statePath = String(opts.state || defaultStatePath);
18061
18141
  if (existsSync16(statePath)) {
18062
18142
  try {
18063
- const raw = readFileSync3(statePath, "utf8");
18064
- const parsed = JSON.parse(raw);
18143
+ const raw2 = readFileSync3(statePath, "utf8");
18144
+ const parsed = JSON.parse(raw2);
18065
18145
  previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
18066
18146
  if (previous) {
18067
18147
  info("-- Comparing with legacy state file (will migrate to new snapshot format)");
@@ -24651,10 +24731,10 @@ class BelongsToManyRelationBuilder {
24651
24731
  hydrateRows(rows) {
24652
24732
  const fkParent = this.fkParent;
24653
24733
  const fkRelated = this.fkRelated;
24654
- return rows.map((raw) => {
24734
+ return rows.map((raw2) => {
24655
24735
  const relatedRow = {};
24656
24736
  const pivotExtras = {};
24657
- for (const [k2, v2] of Object.entries(raw)) {
24737
+ for (const [k2, v2] of Object.entries(raw2)) {
24658
24738
  if (k2.startsWith(BTM_RELATED_ALIAS))
24659
24739
  relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
24660
24740
  else if (k2 !== fkParent && k2 !== fkRelated)
@@ -29120,11 +29200,11 @@ Received ${signal}, cleaning up...`);
29120
29200
  argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
29121
29201
  argv = argv.slice(0, doubleDashesIndex);
29122
29202
  }
29123
- const raw = parseArgv(argv, mriOptions);
29124
- const parsed = { _: raw._ };
29125
- for (const name of Object.keys(raw)) {
29203
+ const raw2 = parseArgv(argv, mriOptions);
29204
+ const parsed = { _: raw2._ };
29205
+ for (const name of Object.keys(raw2)) {
29126
29206
  if (name !== "_") {
29127
- parsed[camelcaseOptionName(name)] = raw[name];
29207
+ parsed[camelcaseOptionName(name)] = raw2[name];
29128
29208
  }
29129
29209
  }
29130
29210
  const args = parsed._;
@@ -29169,11 +29249,11 @@ Received ${signal}, cleaning up...`);
29169
29249
  if (!isUsage)
29170
29250
  return;
29171
29251
  const e2 = err;
29172
- const raw = e2.message ?? "command-line error";
29252
+ const raw2 = e2.message ?? "command-line error";
29173
29253
  const label = this.name ? `${this.name}: ` : "";
29174
- const suffix = /--help/.test(raw) ? "" : `
29254
+ const suffix = /--help/.test(raw2) ? "" : `
29175
29255
  Run \`${this.name ?? "cli"} --help\` for usage.`;
29176
- process52.stderr.write(`${label}${raw}${suffix}
29256
+ process52.stderr.write(`${label}${raw2}${suffix}
29177
29257
  `);
29178
29258
  process52.exit(e2.exitCode ?? 2);
29179
29259
  }
@@ -30031,7 +30111,7 @@ function getPrefix() {
30031
30111
  }
30032
30112
  var prefix = getPrefix();
30033
30113
  // package.json
30034
- var version2 = "0.1.31";
30114
+ var version2 = "0.1.33";
30035
30115
 
30036
30116
  // bin/cli.ts
30037
30117
  init_actions();
package/dist/client.d.ts CHANGED
@@ -2,6 +2,33 @@ import { config } from './config';
2
2
  import { resetConnection } from './db';
3
3
  import type { DatabaseSchema } from './schema';
4
4
  import type { SchemaMeta } from './meta';
5
+ /**
6
+ * # `raw`
7
+ *
8
+ * Build a raw SQL fragment for the `*Raw` builder methods (`whereRaw`,
9
+ * `selectRaw`, `orderByRaw`, `groupByRaw`, `havingRaw`) and `select()`.
10
+ *
11
+ * Use this INSTEAD of a Bun `sql\`...\`` tag: a Bun query object cannot be
12
+ * converted back to SQL text (it stringifies to "[object Promise]"), so it
13
+ * silently corrupts the generated SQL. `raw` returns a `{ raw: string }`
14
+ * fragment that the builder renders correctly and that satisfies the
15
+ * `SqlFragment` type (so it passes the bare-string injection guard).
16
+ *
17
+ * Interpolated values in the tagged-template form are SQL-escaped (strings
18
+ * single-quote-doubled, dates → ISO, numbers/booleans/null inlined) — the
19
+ * same escaping the relation-subquery builders use. For user input that must
20
+ * be parameterised, prefer the typed `where(...)` methods over `raw`.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { raw } from 'bun-query-builder'
25
+ * db.selectFrom('users').selectRaw(raw`count(*) as c`)
26
+ * db.selectFrom('users').whereRaw(raw('age > 18'))
27
+ * db.selectFrom('users').orderByRaw(raw`created_at desc`)
28
+ * db.selectFrom('orders').whereRaw(raw`status = ${userStatus}`) // value escaped
29
+ * ```
30
+ */
31
+ export declare function raw(strings: TemplateStringsArray | string, ...values: unknown[]): RawExpression;
5
32
  // eslint-disable-next-line pickier/no-unused-vars
6
33
  export declare function createQueryBuilder<DB extends DatabaseSchema<any>>(state?: Partial<InternalState>): QueryBuilder<DB>;
7
34
  /**
@@ -26,6 +53,10 @@ export declare function clearQueryCache(): void;
26
53
  * ```
27
54
  */
28
55
  export declare function setQueryCacheMaxSize(size: number): void;
56
+ // Type guard for raw SQL expressions
57
+ declare interface RawExpression {
58
+ raw: string
59
+ }
29
60
  export declare interface WhereRaw {
30
61
  raw: any
31
62
  }
package/dist/src/index.js CHANGED
@@ -12344,6 +12344,14 @@ function* iterateAllPivots(meta, options = {}) {
12344
12344
  function isRawExpression(expr) {
12345
12345
  return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
12346
12346
  }
12347
+ function raw(strings, ...values) {
12348
+ if (typeof strings === "string")
12349
+ return { raw: strings };
12350
+ let out = strings[0];
12351
+ for (let i = 0;i < values.length; i++)
12352
+ out += formatSubqueryValue(values[i]) + strings[i + 1];
12353
+ return { raw: out };
12354
+ }
12347
12355
  function quoteInsertIdent(id) {
12348
12356
  return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
12349
12357
  }
@@ -12412,13 +12420,31 @@ function validateQualifiedIdentifier(value, context) {
12412
12420
  throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
12413
12421
  }
12414
12422
  }
12415
- function assertSqlFragment(fragment, context) {
12416
- if (fragment === null || fragment === undefined) {
12417
- throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
12418
- }
12423
+ function renderRawFragment(fragment, context) {
12419
12424
  if (typeof fragment === "string") {
12420
12425
  warnOnceBareSqlFragment(context);
12426
+ return fragment;
12421
12427
  }
12428
+ if (fragment === null || fragment === undefined)
12429
+ throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
12430
+ if (isRawExpression(fragment))
12431
+ return fragment.raw;
12432
+ if (typeof fragment === "object") {
12433
+ const f = fragment;
12434
+ if (typeof f.raw === "string")
12435
+ return f.raw;
12436
+ if (typeof f.raw === "function") {
12437
+ const r = f.raw();
12438
+ if (typeof r === "string")
12439
+ return r;
12440
+ }
12441
+ if (typeof f.sql === "string")
12442
+ return f.sql;
12443
+ const s = String(fragment);
12444
+ if (s !== "[object Object]" && s !== "[object Promise]")
12445
+ return s;
12446
+ }
12447
+ throw new TypeError(`[query-builder] ${context}: cannot render this value as a SQL fragment. ` + `A Bun \`sql\`...\`\` query object cannot be converted to SQL text \u2014 pass a ` + `string, or use the exported \`raw\` helper: raw\`count(*) as c\` / raw('age > 18').`);
12422
12448
  }
12423
12449
  function warnOnceBareSqlFragment(context) {
12424
12450
  if (warnedSqlFragmentContexts.has(context))
@@ -12809,7 +12835,7 @@ function createQueryBuilder(state) {
12809
12835
  };
12810
12836
  const addWhereText = (prefix, clause) => {
12811
12837
  const hasWhere = SQL_PATTERNS.WHERE.test(text);
12812
- const p = hasWhere ? prefix : "WHERE";
12838
+ const p = !hasWhere ? "WHERE" : prefix === "WHERE" ? "AND" : prefix;
12813
12839
  text = `${text} ${p} ${clause}`;
12814
12840
  };
12815
12841
  const appendSetOp = (op, other) => {
@@ -13093,12 +13119,12 @@ function createQueryBuilder(state) {
13093
13119
  return this;
13094
13120
  },
13095
13121
  selectRaw(fragment) {
13096
- assertSqlFragment(fragment, "selectRaw(fragment)");
13122
+ const frag = renderRawFragment(fragment, "selectRaw(fragment)");
13097
13123
  const fromIdx = text.indexOf(" FROM ");
13098
13124
  if (fromIdx !== -1) {
13099
- text = `${text.substring(0, fromIdx)}, ${String(fragment)}${text.substring(fromIdx)}`;
13125
+ text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
13100
13126
  } else {
13101
- text += `, ${String(fragment)}`;
13127
+ text += `, ${frag}`;
13102
13128
  }
13103
13129
  built = null;
13104
13130
  return this;
@@ -13211,12 +13237,15 @@ function createQueryBuilder(state) {
13211
13237
  if (cols.length === 0)
13212
13238
  return this;
13213
13239
  const rendered = cols.map(renderSelectColumn);
13240
+ const distinctMatch = /^SELECT\s+(DISTINCT(?:\s+ON\s+\([^)]*\))?\s+)/i.exec(text);
13241
+ const distinctPrefix = distinctMatch ? distinctMatch[1] : "";
13214
13242
  const fromIndex = text.indexOf(" FROM ");
13215
13243
  if (fromIndex !== -1) {
13216
- text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
13244
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
13217
13245
  } else {
13218
- text = `SELECT ${rendered.join(", ")} FROM ${table}`;
13246
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
13219
13247
  }
13248
+ built = null;
13220
13249
  return this;
13221
13250
  },
13222
13251
  addSelect(...columns2) {
@@ -13964,87 +13993,71 @@ function createQueryBuilder(state) {
13964
13993
  return this;
13965
13994
  },
13966
13995
  whereLike(column, pattern, caseSensitive = false) {
13967
- const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13968
- built = sql`${ensureBuilt()} WHERE ${expr}`;
13969
13996
  const ph = getPlaceholder(whereParams.length + 1);
13970
13997
  addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13971
13998
  whereParams.push(pattern);
13999
+ built = null;
13972
14000
  return this;
13973
14001
  },
13974
14002
  whereILike(column, pattern) {
13975
14003
  const ph = getPlaceholder(whereParams.length + 1);
13976
- if (config5.dialect === "postgres") {
13977
- built = sql`${ensureBuilt()} WHERE ${sql(String(column))} ILIKE ${pattern}`;
14004
+ if (config5.dialect === "postgres")
13978
14005
  addWhereText("WHERE", `${String(column)} ILIKE ${ph}`);
13979
- } else {
13980
- const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13981
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14006
+ else
13982
14007
  addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
13983
- }
13984
14008
  whereParams.push(pattern);
14009
+ built = null;
13985
14010
  return this;
13986
14011
  },
13987
14012
  orWhereLike(column, pattern, caseSensitive = false) {
13988
- const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
13989
- built = sql`${ensureBuilt()} OR ${expr}`;
13990
14013
  const ph = getPlaceholder(whereParams.length + 1);
13991
14014
  addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
13992
14015
  whereParams.push(pattern);
14016
+ built = null;
13993
14017
  return this;
13994
14018
  },
13995
14019
  orWhereILike(column, pattern) {
13996
14020
  const ph = getPlaceholder(whereParams.length + 1);
13997
- if (config5.dialect === "postgres") {
13998
- built = sql`${ensureBuilt()} OR ${sql(String(column))} ILIKE ${pattern}`;
14021
+ if (config5.dialect === "postgres")
13999
14022
  addWhereText("OR", `${String(column)} ILIKE ${ph}`);
14000
- } else {
14001
- const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
14002
- built = sql`${ensureBuilt()} OR ${expr}`;
14023
+ else
14003
14024
  addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
14004
- }
14005
14025
  whereParams.push(pattern);
14026
+ built = null;
14006
14027
  return this;
14007
14028
  },
14008
14029
  whereNotLike(column, pattern, caseSensitive = false) {
14009
- const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14010
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14011
14030
  const ph = getPlaceholder(whereParams.length + 1);
14012
14031
  addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
14013
14032
  whereParams.push(pattern);
14033
+ built = null;
14014
14034
  return this;
14015
14035
  },
14016
14036
  whereNotILike(column, pattern) {
14017
14037
  const ph = getPlaceholder(whereParams.length + 1);
14018
- if (config5.dialect === "postgres") {
14019
- built = sql`${ensureBuilt()} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
14038
+ if (config5.dialect === "postgres")
14020
14039
  addWhereText("WHERE", `${String(column)} NOT ILIKE ${ph}`);
14021
- } else {
14022
- const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14023
- built = sql`${ensureBuilt()} WHERE ${expr}`;
14040
+ else
14024
14041
  addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
14025
- }
14026
14042
  whereParams.push(pattern);
14043
+ built = null;
14027
14044
  return this;
14028
14045
  },
14029
14046
  orWhereNotLike(column, pattern, caseSensitive = false) {
14030
- const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14031
- built = sql`${ensureBuilt()} OR ${expr}`;
14032
14047
  const ph = getPlaceholder(whereParams.length + 1);
14033
14048
  addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
14034
14049
  whereParams.push(pattern);
14050
+ built = null;
14035
14051
  return this;
14036
14052
  },
14037
14053
  orWhereNotILike(column, pattern) {
14038
14054
  const ph = getPlaceholder(whereParams.length + 1);
14039
- if (config5.dialect === "postgres") {
14040
- built = sql`${ensureBuilt()} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
14055
+ if (config5.dialect === "postgres")
14041
14056
  addWhereText("OR", `${String(column)} NOT ILIKE ${ph}`);
14042
- } else {
14043
- const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
14044
- built = sql`${ensureBuilt()} OR ${expr}`;
14057
+ else
14045
14058
  addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
14046
- }
14047
14059
  whereParams.push(pattern);
14060
+ built = null;
14048
14061
  return this;
14049
14062
  },
14050
14063
  whereAny(cols, op, value) {
@@ -14105,9 +14118,9 @@ function createQueryBuilder(state) {
14105
14118
  return this;
14106
14119
  },
14107
14120
  whereRaw(fragment) {
14108
- assertSqlFragment(fragment, "whereRaw(fragment)");
14121
+ const frag = renderRawFragment(fragment, "whereRaw(fragment)");
14109
14122
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
14110
- text += ` ${keyword} ${String(fragment)}`;
14123
+ text += ` ${keyword} ${frag}`;
14111
14124
  built = null;
14112
14125
  return this;
14113
14126
  },
@@ -14473,8 +14486,8 @@ function createQueryBuilder(state) {
14473
14486
  return this;
14474
14487
  },
14475
14488
  groupByRaw(fragment) {
14476
- assertSqlFragment(fragment, "groupByRaw(fragment)");
14477
- text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} GROUP BY ${String(fragment)}`;
14489
+ const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
14490
+ text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
14478
14491
  built = null;
14479
14492
  return this;
14480
14493
  },
@@ -14506,15 +14519,15 @@ function createQueryBuilder(state) {
14506
14519
  return this;
14507
14520
  },
14508
14521
  havingRaw(fragment) {
14509
- assertSqlFragment(fragment, "havingRaw(fragment)");
14522
+ const frag = renderRawFragment(fragment, "havingRaw(fragment)");
14510
14523
  const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14511
- text += ` ${kw} ${String(fragment)}`;
14524
+ text += ` ${kw} ${frag}`;
14512
14525
  built = null;
14513
14526
  return this;
14514
14527
  },
14515
14528
  orderByRaw(fragment) {
14516
- assertSqlFragment(fragment, "orderByRaw(fragment)");
14517
- text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} ORDER BY ${String(fragment)}`;
14529
+ const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
14530
+ text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
14518
14531
  built = null;
14519
14532
  return this;
14520
14533
  },
@@ -14644,19 +14657,21 @@ function createQueryBuilder(state) {
14644
14657
  q = sql`${q} ORDER BY ${sql(String(column))} ${direction === "asc" ? sql`ASC` : sql`DESC`} LIMIT ${perPage + 1}`;
14645
14658
  }
14646
14659
  const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
14647
- const next = rows.length > perPage ? Array.isArray(column) ? column.map((c) => rows[perPage]?.[c]) : rows[perPage]?.[column] : null;
14660
+ const hasMore = rows.length > perPage;
14648
14661
  const data = rows.slice(0, perPage);
14662
+ const lastRow = data[data.length - 1];
14663
+ const next = hasMore && lastRow ? Array.isArray(column) ? column.map((c) => lastRow[c]) : lastRow[column] : null;
14649
14664
  const prevCursor = data.length ? Array.isArray(column) ? column.map((c) => data[0]?.[c]) : data[0]?.[column] : null;
14650
14665
  return { data, meta: { perPage, nextCursor: next ?? null, prevCursor } };
14651
14666
  },
14652
14667
  async chunk(size, handler) {
14653
14668
  let page = 1;
14654
14669
  while (true) {
14655
- const { data } = await this.paginate(size, page);
14670
+ const { data, meta: meta2 } = await this.paginate(size, page);
14656
14671
  if (data.length === 0)
14657
14672
  break;
14658
14673
  await handler(data);
14659
- if (data.length < size)
14674
+ if (page >= meta2.lastPage)
14660
14675
  break;
14661
14676
  page += 1;
14662
14677
  }
@@ -14697,20 +14712,20 @@ function createQueryBuilder(state) {
14697
14712
  includeTrashed = true;
14698
14713
  onlyTrashed = true;
14699
14714
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14700
- const splice = (raw, predicate2) => {
14701
- const upper = raw.toUpperCase();
14715
+ const splice = (raw2, predicate2) => {
14716
+ const upper = raw2.toUpperCase();
14702
14717
  let depth = 0;
14703
- for (let i = 0;i < raw.length; i++) {
14704
- const c = raw[i];
14718
+ for (let i = 0;i < raw2.length; i++) {
14719
+ const c = raw2[i];
14705
14720
  if (c === "(")
14706
14721
  depth++;
14707
14722
  else if (c === ")")
14708
14723
  depth--;
14709
- else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
14710
- return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
14724
+ else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
14725
+ return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
14711
14726
  }
14712
14727
  }
14713
- return `${raw} WHERE ${predicate2}`;
14728
+ return `${raw2} WHERE ${predicate2}`;
14714
14729
  };
14715
14730
  const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
14716
14731
  text = splice(text, predicate);
@@ -15014,13 +15029,13 @@ function createQueryBuilder(state) {
15014
15029
  const cacheKey = `${String(table)}|${prop}`;
15015
15030
  let chosen = dynamicWhereColumnCache.get(cacheKey);
15016
15031
  if (chosen === undefined) {
15017
- const raw = prop.replace(/^(?:or|and)?where/i, "");
15018
- if (!raw) {
15032
+ const raw2 = prop.replace(/^(?:or|and)?where/i, "");
15033
+ if (!raw2) {
15019
15034
  dynamicWhereColumnCache.set(cacheKey, "");
15020
15035
  chosen = "";
15021
15036
  } else {
15022
- const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
15023
- const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15037
+ const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
15038
+ const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15024
15039
  const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
15025
15040
  chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
15026
15041
  dynamicWhereColumnCache.set(cacheKey, chosen);
@@ -15410,6 +15425,10 @@ function createQueryBuilder(state) {
15410
15425
  returning(...cols) {
15411
15426
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15412
15427
  const q = _sql.unsafe(returningSql, params);
15428
+ const runFirst = async () => {
15429
+ const rows = await runWithHooks(q, "insert");
15430
+ return Array.isArray(rows) ? rows[0] : rows;
15431
+ };
15413
15432
  return {
15414
15433
  where: () => this,
15415
15434
  andWhere: () => this,
@@ -15418,7 +15437,22 @@ function createQueryBuilder(state) {
15418
15437
  limit: () => this,
15419
15438
  offset: () => this,
15420
15439
  toSQL: () => makeExecutableQuery(q, returningSql),
15421
- execute: () => runWithHooks(q, "insert")
15440
+ execute: () => runWithHooks(q, "insert"),
15441
+ get: () => runWithHooks(q, "insert"),
15442
+ first: runFirst,
15443
+ executeTakeFirst: runFirst,
15444
+ async firstOrFail() {
15445
+ const row = await runFirst();
15446
+ if (!row)
15447
+ throw new Error("Insert with RETURNING returned no rows");
15448
+ return row;
15449
+ },
15450
+ async executeTakeFirstOrThrow() {
15451
+ const row = await runFirst();
15452
+ if (!row)
15453
+ throw new Error("Insert with RETURNING returned no rows");
15454
+ return row;
15455
+ }
15422
15456
  };
15423
15457
  },
15424
15458
  toSQL() {
@@ -15457,19 +15491,27 @@ function createQueryBuilder(state) {
15457
15491
  returningAll() {
15458
15492
  const returningSql = `${sqlText} RETURNING *`;
15459
15493
  const q = _sql.unsafe(returningSql, params);
15494
+ const runFirst = async () => {
15495
+ const result = await runWithHooks(q, "insert");
15496
+ return Array.isArray(result) ? result[0] : result;
15497
+ };
15460
15498
  return {
15461
15499
  toSQL: () => makeExecutableQuery(q, returningSql),
15462
15500
  execute: () => runWithHooks(q, "insert"),
15463
- async executeTakeFirst() {
15464
- const result = await runWithHooks(q, "insert");
15465
- return Array.isArray(result) ? result[0] : result;
15501
+ get: () => runWithHooks(q, "insert"),
15502
+ first: runFirst,
15503
+ executeTakeFirst: runFirst,
15504
+ async firstOrFail() {
15505
+ const row = await runFirst();
15506
+ if (!row)
15507
+ throw new Error("Insert with RETURNING returned no rows");
15508
+ return row;
15466
15509
  },
15467
15510
  async executeTakeFirstOrThrow() {
15468
- const result = await runWithHooks(q, "insert");
15469
- const first = Array.isArray(result) ? result[0] : result;
15470
- if (!first)
15471
- throw new Error("Insert with RETURNING failed");
15472
- return first;
15511
+ const row = await runFirst();
15512
+ if (!row)
15513
+ throw new Error("Insert with RETURNING returned no rows");
15514
+ return row;
15473
15515
  }
15474
15516
  };
15475
15517
  }
@@ -15533,6 +15575,10 @@ function createQueryBuilder(state) {
15533
15575
  returning(...cols) {
15534
15576
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15535
15577
  const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
15578
+ const runFirst = async () => {
15579
+ const rows = await runWithHooks(q, "update");
15580
+ return Array.isArray(rows) ? rows[0] : rows;
15581
+ };
15536
15582
  const obj = {
15537
15583
  where: () => obj,
15538
15584
  andWhere: () => obj,
@@ -15541,7 +15587,22 @@ function createQueryBuilder(state) {
15541
15587
  limit: () => obj,
15542
15588
  offset: () => obj,
15543
15589
  toSQL: () => makeExecutableQuery(q, retText),
15544
- execute: () => runWithHooks(q, "update")
15590
+ execute: () => runWithHooks(q, "update"),
15591
+ get: () => runWithHooks(q, "update"),
15592
+ first: runFirst,
15593
+ executeTakeFirst: runFirst,
15594
+ async firstOrFail() {
15595
+ const row = await runFirst();
15596
+ if (!row)
15597
+ throw new Error("Update with RETURNING returned no rows");
15598
+ return row;
15599
+ },
15600
+ async executeTakeFirstOrThrow() {
15601
+ const row = await runFirst();
15602
+ if (!row)
15603
+ throw new Error("Update with RETURNING returned no rows");
15604
+ return row;
15605
+ }
15545
15606
  };
15546
15607
  return obj;
15547
15608
  },
@@ -15640,6 +15701,10 @@ function createQueryBuilder(state) {
15640
15701
  returning(...cols) {
15641
15702
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15642
15703
  const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
15704
+ const runFirst = async () => {
15705
+ const rows = await runWithHooks(q, "delete");
15706
+ return Array.isArray(rows) ? rows[0] : rows;
15707
+ };
15643
15708
  const obj = {
15644
15709
  where: () => obj,
15645
15710
  andWhere: () => obj,
@@ -15648,7 +15713,22 @@ function createQueryBuilder(state) {
15648
15713
  limit: () => obj,
15649
15714
  offset: () => obj,
15650
15715
  toSQL: () => makeExecutableQuery(q, retText),
15651
- execute: () => runWithHooks(q, "delete")
15716
+ execute: () => runWithHooks(q, "delete"),
15717
+ get: () => runWithHooks(q, "delete"),
15718
+ first: runFirst,
15719
+ executeTakeFirst: runFirst,
15720
+ async firstOrFail() {
15721
+ const row = await runFirst();
15722
+ if (!row)
15723
+ throw new Error("Delete with RETURNING returned no rows");
15724
+ return row;
15725
+ },
15726
+ async executeTakeFirstOrThrow() {
15727
+ const row = await runFirst();
15728
+ if (!row)
15729
+ throw new Error("Delete with RETURNING returned no rows");
15730
+ return row;
15731
+ }
15652
15732
  };
15653
15733
  return obj;
15654
15734
  },
@@ -18000,8 +18080,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
18000
18080
  return;
18001
18081
  }
18002
18082
  try {
18003
- const raw = readFileSync3(snapshotPath, "utf8");
18004
- const parsed = JSON.parse(raw);
18083
+ const raw2 = readFileSync3(snapshotPath, "utf8");
18084
+ const parsed = JSON.parse(raw2);
18005
18085
  if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
18006
18086
  return parsed.plan;
18007
18087
  }
@@ -18060,8 +18140,8 @@ async function generateMigration(dir, opts = {}) {
18060
18140
  const statePath = String(opts.state || defaultStatePath);
18061
18141
  if (existsSync16(statePath)) {
18062
18142
  try {
18063
- const raw = readFileSync3(statePath, "utf8");
18064
- const parsed = JSON.parse(raw);
18143
+ const raw2 = readFileSync3(statePath, "utf8");
18144
+ const parsed = JSON.parse(raw2);
18065
18145
  previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
18066
18146
  if (previous) {
18067
18147
  info("-- Comparing with legacy state file (will migrate to new snapshot format)");
@@ -24651,10 +24731,10 @@ class BelongsToManyRelationBuilder {
24651
24731
  hydrateRows(rows) {
24652
24732
  const fkParent = this.fkParent;
24653
24733
  const fkRelated = this.fkRelated;
24654
- return rows.map((raw) => {
24734
+ return rows.map((raw2) => {
24655
24735
  const relatedRow = {};
24656
24736
  const pivotExtras = {};
24657
- for (const [k2, v2] of Object.entries(raw)) {
24737
+ for (const [k2, v2] of Object.entries(raw2)) {
24658
24738
  if (k2.startsWith(BTM_RELATED_ALIAS))
24659
24739
  relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
24660
24740
  else if (k2 !== fkParent && k2 !== fkRelated)
@@ -28021,6 +28101,7 @@ export {
28021
28101
  relationDiagram,
28022
28102
  registerModel,
28023
28103
  registerBrowserModels,
28104
+ raw,
28024
28105
  queryExplainAll,
28025
28106
  ping,
28026
28107
  parseStacksModels,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.1.31",
4
+ "version": "0.1.33",
5
5
  "description": "A simple yet performant query builder for TypeScript. Built with Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",