bun-query-builder 0.1.30 → 0.1.32

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))
@@ -12435,6 +12461,8 @@ function formatSubqueryValue(val) {
12435
12461
  return val ? "1" : "0";
12436
12462
  if (typeof val === "string")
12437
12463
  return `'${val.replace(/'/g, "''")}'`;
12464
+ if (val instanceof Date)
12465
+ return `'${val.toISOString()}'`;
12438
12466
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
12439
12467
  }
12440
12468
  function buildOverClause(partitionBy, orderBy) {
@@ -13091,12 +13119,12 @@ function createQueryBuilder(state) {
13091
13119
  return this;
13092
13120
  },
13093
13121
  selectRaw(fragment) {
13094
- assertSqlFragment(fragment, "selectRaw(fragment)");
13122
+ const frag = renderRawFragment(fragment, "selectRaw(fragment)");
13095
13123
  const fromIdx = text.indexOf(" FROM ");
13096
13124
  if (fromIdx !== -1) {
13097
- text = `${text.substring(0, fromIdx)}, ${String(fragment)}${text.substring(fromIdx)}`;
13125
+ text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
13098
13126
  } else {
13099
- text += `, ${String(fragment)}`;
13127
+ text += `, ${frag}`;
13100
13128
  }
13101
13129
  built = null;
13102
13130
  return this;
@@ -13209,12 +13237,15 @@ function createQueryBuilder(state) {
13209
13237
  if (cols.length === 0)
13210
13238
  return this;
13211
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] : "";
13212
13242
  const fromIndex = text.indexOf(" FROM ");
13213
13243
  if (fromIndex !== -1) {
13214
- text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
13244
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
13215
13245
  } else {
13216
- text = `SELECT ${rendered.join(", ")} FROM ${table}`;
13246
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
13217
13247
  }
13248
+ built = null;
13218
13249
  return this;
13219
13250
  },
13220
13251
  addSelect(...columns2) {
@@ -13292,6 +13323,54 @@ function createQueryBuilder(state) {
13292
13323
  }
13293
13324
  return "";
13294
13325
  };
13326
+ const buildJoinConstraint = (targetTbl) => {
13327
+ if (!condition)
13328
+ return "";
13329
+ const frags = [];
13330
+ const addCmp = (col, op, val) => {
13331
+ validateIdentifier(String(col), "with() constraint column");
13332
+ const operator = assertSafeWhereOperator(op, "with() constraint operator");
13333
+ frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
13334
+ };
13335
+ const unsupported = (m) => () => {
13336
+ throw new Error(`[query-builder] with('${relationKey}', ...): ${m} is not supported inside a constraint callback on the JOIN-based builder \u2014 apply it to the outer query, or use the model layer's eager loading. (Silently ignoring it would return wrong data.)`);
13337
+ };
13338
+ const constraintQb = {
13339
+ where: (expr, op, val) => {
13340
+ if (Array.isArray(expr))
13341
+ addCmp(expr[0], expr[1], expr[2]);
13342
+ else if (expr && typeof expr === "object")
13343
+ for (const k of Object.keys(expr))
13344
+ addCmp(k, "=", expr[k]);
13345
+ else if (op !== undefined && val !== undefined)
13346
+ addCmp(expr, op, val);
13347
+ else if (op !== undefined)
13348
+ addCmp(expr, "=", op);
13349
+ return constraintQb;
13350
+ },
13351
+ whereIn: (col, vals) => {
13352
+ validateIdentifier(String(col), "with() constraint column");
13353
+ frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
13354
+ return constraintQb;
13355
+ },
13356
+ whereNull: (col) => {
13357
+ validateIdentifier(String(col), "with() constraint column");
13358
+ frags.push(`${targetTbl}.${String(col)} IS NULL`);
13359
+ return constraintQb;
13360
+ },
13361
+ whereNotNull: (col) => {
13362
+ validateIdentifier(String(col), "with() constraint column");
13363
+ frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
13364
+ return constraintQb;
13365
+ },
13366
+ orderBy: unsupported("orderBy()"),
13367
+ limit: unsupported("limit()"),
13368
+ offset: unsupported("offset()"),
13369
+ take: unsupported("take()")
13370
+ };
13371
+ condition(constraintQb);
13372
+ return frags.length ? ` AND ${frags.join(" AND ")}` : "";
13373
+ };
13295
13374
  const resolveTarget = () => {
13296
13375
  const pick = (m) => {
13297
13376
  const modelName = m?.[relationKey];
@@ -13336,7 +13415,7 @@ function createQueryBuilder(state) {
13336
13415
  const throughPk = meta.primaryKeys[throughTable] ?? "id";
13337
13416
  const fkInThrough = `${singularize(fromTable)}_id`;
13338
13417
  const fkInFinal = `${singularize(throughTable)}_id`;
13339
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(throughTable)} ON ${sql(`${throughTable}.${fkInThrough}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(finalTable)} ON ${sql(`${finalTable}.${fkInFinal}`)} = ${sql(`${throughTable}.${throughPk}`)}`;
13418
+ insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
13340
13419
  joinedTables.add(throughTable);
13341
13420
  joinedTables.add(finalTable);
13342
13421
  return finalTable;
@@ -13349,7 +13428,7 @@ function createQueryBuilder(state) {
13349
13428
  const childPk = meta.primaryKeys[childTable] ?? "id";
13350
13429
  const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
13351
13430
  const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
13352
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivot)} ON ${sql(`${pivot}.${fkA}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${childPk}`)} = ${sql(`${pivot}.${fkB}`)}`;
13431
+ insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
13353
13432
  joinedTables.add(pivot);
13354
13433
  joinedTables.add(childTable);
13355
13434
  return childTable;
@@ -13363,7 +13442,8 @@ function createQueryBuilder(state) {
13363
13442
  const morphType = `${morphName}_type`;
13364
13443
  const morphId = `${morphName}_id`;
13365
13444
  const targetFk = `${singularize(childTable)}_id`;
13366
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivotTable)} ON ${sql(`${pivotTable}.${morphId}`)} = ${sql(`${fromTable}.${fromPk}`)} AND ${sql(`${pivotTable}.${morphType}`)} = ${sql(meta.tableToModel[fromTable] || fromTable)} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${childPk}`)} = ${sql(`${pivotTable}.${targetFk}`)}`;
13445
+ const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
13446
+ insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
13367
13447
  joinedTables.add(pivotTable);
13368
13448
  joinedTables.add(childTable);
13369
13449
  return childTable;
@@ -13379,7 +13459,8 @@ function createQueryBuilder(state) {
13379
13459
  const morphType = `${morphName}_type`;
13380
13460
  const morphId = `${morphName}_id`;
13381
13461
  const relatedFk = `${singularize(relatedTable)}_id`;
13382
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivotTable)} ON ${sql(`${pivotTable}.${relatedFk}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(relatedTable)} ON ${sql(`${relatedTable}.${relatedPk}`)} = ${sql(`${pivotTable}.${morphId}`)} AND ${sql(`${pivotTable}.${morphType}`)} = ${sql(meta.tableToModel[relatedTable] || relatedTable)}`;
13462
+ const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
13463
+ insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
13383
13464
  joinedTables.add(pivotTable);
13384
13465
  joinedTables.add(relatedTable);
13385
13466
  return relatedTable;
@@ -13388,7 +13469,7 @@ function createQueryBuilder(state) {
13388
13469
  if (isBt) {
13389
13470
  const fkInParent = `${singularize(childTable)}_id`;
13390
13471
  const childPk = meta.primaryKeys[childTable] ?? "id";
13391
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${fromTable}.${fkInParent}`)} = ${sql(`${childTable}.${childPk}`)}`;
13472
+ insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
13392
13473
  joinedTables.add(childTable);
13393
13474
  return childTable;
13394
13475
  }
@@ -13398,20 +13479,15 @@ function createQueryBuilder(state) {
13398
13479
  const morphType = `${relationKey}_type`;
13399
13480
  const morphId = `${relationKey}_id`;
13400
13481
  const fromPk = meta.primaryKeys[fromTable] ?? "id";
13401
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${morphId}`)} = ${sql(`${fromTable}.${fromPk}`)} AND ${sql(`${childTable}.${morphType}`)} = ${sql(meta.tableToModel[fromTable] || fromTable)}`;
13482
+ const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
13483
+ insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
13402
13484
  joinedTables.add(childTable);
13403
13485
  return childTable;
13404
13486
  }
13405
13487
  const fkInChild = `${singularize(fromTable)}_id`;
13406
13488
  const pk = meta.primaryKeys[fromTable] ?? "id";
13407
- const softDeleteCheck = addSoftDeleteCheck(childTable);
13408
- if (softDeleteCheck) {
13409
- const currentSql = String(ensureBuilt());
13410
- const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
13411
- built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
13412
- } else {
13413
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
13414
- }
13489
+ const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
13490
+ insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
13415
13491
  joinedTables.add(childTable);
13416
13492
  return childTable;
13417
13493
  };
@@ -13448,7 +13524,7 @@ function createQueryBuilder(state) {
13448
13524
  addToSelectClause(pivotColumnsStr);
13449
13525
  }
13450
13526
  }
13451
- text = computeSqlText(ensureBuilt());
13527
+ built = null;
13452
13528
  return this;
13453
13529
  },
13454
13530
  whereHas(relation, callback) {
@@ -14058,9 +14134,9 @@ function createQueryBuilder(state) {
14058
14134
  return this;
14059
14135
  },
14060
14136
  whereRaw(fragment) {
14061
- assertSqlFragment(fragment, "whereRaw(fragment)");
14137
+ const frag = renderRawFragment(fragment, "whereRaw(fragment)");
14062
14138
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
14063
- text += ` ${keyword} ${String(fragment)}`;
14139
+ text += ` ${keyword} ${frag}`;
14064
14140
  built = null;
14065
14141
  return this;
14066
14142
  },
@@ -14426,8 +14502,8 @@ function createQueryBuilder(state) {
14426
14502
  return this;
14427
14503
  },
14428
14504
  groupByRaw(fragment) {
14429
- assertSqlFragment(fragment, "groupByRaw(fragment)");
14430
- text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} GROUP BY ${String(fragment)}`;
14505
+ const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
14506
+ text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
14431
14507
  built = null;
14432
14508
  return this;
14433
14509
  },
@@ -14459,15 +14535,15 @@ function createQueryBuilder(state) {
14459
14535
  return this;
14460
14536
  },
14461
14537
  havingRaw(fragment) {
14462
- assertSqlFragment(fragment, "havingRaw(fragment)");
14538
+ const frag = renderRawFragment(fragment, "havingRaw(fragment)");
14463
14539
  const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14464
- text += ` ${kw} ${String(fragment)}`;
14540
+ text += ` ${kw} ${frag}`;
14465
14541
  built = null;
14466
14542
  return this;
14467
14543
  },
14468
14544
  orderByRaw(fragment) {
14469
- assertSqlFragment(fragment, "orderByRaw(fragment)");
14470
- text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} ORDER BY ${String(fragment)}`;
14545
+ const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
14546
+ text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
14471
14547
  built = null;
14472
14548
  return this;
14473
14549
  },
@@ -14650,20 +14726,20 @@ function createQueryBuilder(state) {
14650
14726
  includeTrashed = true;
14651
14727
  onlyTrashed = true;
14652
14728
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14653
- const splice = (raw, predicate2) => {
14654
- const upper = raw.toUpperCase();
14729
+ const splice = (raw2, predicate2) => {
14730
+ const upper = raw2.toUpperCase();
14655
14731
  let depth = 0;
14656
- for (let i = 0;i < raw.length; i++) {
14657
- const c = raw[i];
14732
+ for (let i = 0;i < raw2.length; i++) {
14733
+ const c = raw2[i];
14658
14734
  if (c === "(")
14659
14735
  depth++;
14660
14736
  else if (c === ")")
14661
14737
  depth--;
14662
- else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
14663
- return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
14738
+ else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
14739
+ return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
14664
14740
  }
14665
14741
  }
14666
- return `${raw} WHERE ${predicate2}`;
14742
+ return `${raw2} WHERE ${predicate2}`;
14667
14743
  };
14668
14744
  const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
14669
14745
  text = splice(text, predicate);
@@ -14967,13 +15043,13 @@ function createQueryBuilder(state) {
14967
15043
  const cacheKey = `${String(table)}|${prop}`;
14968
15044
  let chosen = dynamicWhereColumnCache.get(cacheKey);
14969
15045
  if (chosen === undefined) {
14970
- const raw = prop.replace(/^(?:or|and)?where/i, "");
14971
- if (!raw) {
15046
+ const raw2 = prop.replace(/^(?:or|and)?where/i, "");
15047
+ if (!raw2) {
14972
15048
  dynamicWhereColumnCache.set(cacheKey, "");
14973
15049
  chosen = "";
14974
15050
  } else {
14975
- const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
14976
- const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15051
+ const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
15052
+ const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
14977
15053
  const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
14978
15054
  chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
14979
15055
  dynamicWhereColumnCache.set(cacheKey, chosen);
@@ -15363,6 +15439,10 @@ function createQueryBuilder(state) {
15363
15439
  returning(...cols) {
15364
15440
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15365
15441
  const q = _sql.unsafe(returningSql, params);
15442
+ const runFirst = async () => {
15443
+ const rows = await runWithHooks(q, "insert");
15444
+ return Array.isArray(rows) ? rows[0] : rows;
15445
+ };
15366
15446
  return {
15367
15447
  where: () => this,
15368
15448
  andWhere: () => this,
@@ -15371,7 +15451,22 @@ function createQueryBuilder(state) {
15371
15451
  limit: () => this,
15372
15452
  offset: () => this,
15373
15453
  toSQL: () => makeExecutableQuery(q, returningSql),
15374
- execute: () => runWithHooks(q, "insert")
15454
+ execute: () => runWithHooks(q, "insert"),
15455
+ get: () => runWithHooks(q, "insert"),
15456
+ first: runFirst,
15457
+ executeTakeFirst: runFirst,
15458
+ async firstOrFail() {
15459
+ const row = await runFirst();
15460
+ if (!row)
15461
+ throw new Error("Insert with RETURNING returned no rows");
15462
+ return row;
15463
+ },
15464
+ async executeTakeFirstOrThrow() {
15465
+ const row = await runFirst();
15466
+ if (!row)
15467
+ throw new Error("Insert with RETURNING returned no rows");
15468
+ return row;
15469
+ }
15375
15470
  };
15376
15471
  },
15377
15472
  toSQL() {
@@ -15410,19 +15505,27 @@ function createQueryBuilder(state) {
15410
15505
  returningAll() {
15411
15506
  const returningSql = `${sqlText} RETURNING *`;
15412
15507
  const q = _sql.unsafe(returningSql, params);
15508
+ const runFirst = async () => {
15509
+ const result = await runWithHooks(q, "insert");
15510
+ return Array.isArray(result) ? result[0] : result;
15511
+ };
15413
15512
  return {
15414
15513
  toSQL: () => makeExecutableQuery(q, returningSql),
15415
15514
  execute: () => runWithHooks(q, "insert"),
15416
- async executeTakeFirst() {
15417
- const result = await runWithHooks(q, "insert");
15418
- return Array.isArray(result) ? result[0] : result;
15515
+ get: () => runWithHooks(q, "insert"),
15516
+ first: runFirst,
15517
+ executeTakeFirst: runFirst,
15518
+ async firstOrFail() {
15519
+ const row = await runFirst();
15520
+ if (!row)
15521
+ throw new Error("Insert with RETURNING returned no rows");
15522
+ return row;
15419
15523
  },
15420
15524
  async executeTakeFirstOrThrow() {
15421
- const result = await runWithHooks(q, "insert");
15422
- const first = Array.isArray(result) ? result[0] : result;
15423
- if (!first)
15424
- throw new Error("Insert with RETURNING failed");
15425
- return first;
15525
+ const row = await runFirst();
15526
+ if (!row)
15527
+ throw new Error("Insert with RETURNING returned no rows");
15528
+ return row;
15426
15529
  }
15427
15530
  };
15428
15531
  }
@@ -15486,6 +15589,10 @@ function createQueryBuilder(state) {
15486
15589
  returning(...cols) {
15487
15590
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15488
15591
  const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
15592
+ const runFirst = async () => {
15593
+ const rows = await runWithHooks(q, "update");
15594
+ return Array.isArray(rows) ? rows[0] : rows;
15595
+ };
15489
15596
  const obj = {
15490
15597
  where: () => obj,
15491
15598
  andWhere: () => obj,
@@ -15494,7 +15601,22 @@ function createQueryBuilder(state) {
15494
15601
  limit: () => obj,
15495
15602
  offset: () => obj,
15496
15603
  toSQL: () => makeExecutableQuery(q, retText),
15497
- execute: () => runWithHooks(q, "update")
15604
+ execute: () => runWithHooks(q, "update"),
15605
+ get: () => runWithHooks(q, "update"),
15606
+ first: runFirst,
15607
+ executeTakeFirst: runFirst,
15608
+ async firstOrFail() {
15609
+ const row = await runFirst();
15610
+ if (!row)
15611
+ throw new Error("Update with RETURNING returned no rows");
15612
+ return row;
15613
+ },
15614
+ async executeTakeFirstOrThrow() {
15615
+ const row = await runFirst();
15616
+ if (!row)
15617
+ throw new Error("Update with RETURNING returned no rows");
15618
+ return row;
15619
+ }
15498
15620
  };
15499
15621
  return obj;
15500
15622
  },
@@ -15593,6 +15715,10 @@ function createQueryBuilder(state) {
15593
15715
  returning(...cols) {
15594
15716
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15595
15717
  const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
15718
+ const runFirst = async () => {
15719
+ const rows = await runWithHooks(q, "delete");
15720
+ return Array.isArray(rows) ? rows[0] : rows;
15721
+ };
15596
15722
  const obj = {
15597
15723
  where: () => obj,
15598
15724
  andWhere: () => obj,
@@ -15601,7 +15727,22 @@ function createQueryBuilder(state) {
15601
15727
  limit: () => obj,
15602
15728
  offset: () => obj,
15603
15729
  toSQL: () => makeExecutableQuery(q, retText),
15604
- execute: () => runWithHooks(q, "delete")
15730
+ execute: () => runWithHooks(q, "delete"),
15731
+ get: () => runWithHooks(q, "delete"),
15732
+ first: runFirst,
15733
+ executeTakeFirst: runFirst,
15734
+ async firstOrFail() {
15735
+ const row = await runFirst();
15736
+ if (!row)
15737
+ throw new Error("Delete with RETURNING returned no rows");
15738
+ return row;
15739
+ },
15740
+ async executeTakeFirstOrThrow() {
15741
+ const row = await runFirst();
15742
+ if (!row)
15743
+ throw new Error("Delete with RETURNING returned no rows");
15744
+ return row;
15745
+ }
15605
15746
  };
15606
15747
  return obj;
15607
15748
  },
@@ -17953,8 +18094,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
17953
18094
  return;
17954
18095
  }
17955
18096
  try {
17956
- const raw = readFileSync3(snapshotPath, "utf8");
17957
- const parsed = JSON.parse(raw);
18097
+ const raw2 = readFileSync3(snapshotPath, "utf8");
18098
+ const parsed = JSON.parse(raw2);
17958
18099
  if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
17959
18100
  return parsed.plan;
17960
18101
  }
@@ -18013,8 +18154,8 @@ async function generateMigration(dir, opts = {}) {
18013
18154
  const statePath = String(opts.state || defaultStatePath);
18014
18155
  if (existsSync16(statePath)) {
18015
18156
  try {
18016
- const raw = readFileSync3(statePath, "utf8");
18017
- const parsed = JSON.parse(raw);
18157
+ const raw2 = readFileSync3(statePath, "utf8");
18158
+ const parsed = JSON.parse(raw2);
18018
18159
  previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
18019
18160
  if (previous) {
18020
18161
  info("-- Comparing with legacy state file (will migrate to new snapshot format)");
@@ -24604,10 +24745,10 @@ class BelongsToManyRelationBuilder {
24604
24745
  hydrateRows(rows) {
24605
24746
  const fkParent = this.fkParent;
24606
24747
  const fkRelated = this.fkRelated;
24607
- return rows.map((raw) => {
24748
+ return rows.map((raw2) => {
24608
24749
  const relatedRow = {};
24609
24750
  const pivotExtras = {};
24610
- for (const [k2, v2] of Object.entries(raw)) {
24751
+ for (const [k2, v2] of Object.entries(raw2)) {
24611
24752
  if (k2.startsWith(BTM_RELATED_ALIAS))
24612
24753
  relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
24613
24754
  else if (k2 !== fkParent && k2 !== fkRelated)
@@ -29073,11 +29214,11 @@ Received ${signal}, cleaning up...`);
29073
29214
  argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
29074
29215
  argv = argv.slice(0, doubleDashesIndex);
29075
29216
  }
29076
- const raw = parseArgv(argv, mriOptions);
29077
- const parsed = { _: raw._ };
29078
- for (const name of Object.keys(raw)) {
29217
+ const raw2 = parseArgv(argv, mriOptions);
29218
+ const parsed = { _: raw2._ };
29219
+ for (const name of Object.keys(raw2)) {
29079
29220
  if (name !== "_") {
29080
- parsed[camelcaseOptionName(name)] = raw[name];
29221
+ parsed[camelcaseOptionName(name)] = raw2[name];
29081
29222
  }
29082
29223
  }
29083
29224
  const args = parsed._;
@@ -29122,11 +29263,11 @@ Received ${signal}, cleaning up...`);
29122
29263
  if (!isUsage)
29123
29264
  return;
29124
29265
  const e2 = err;
29125
- const raw = e2.message ?? "command-line error";
29266
+ const raw2 = e2.message ?? "command-line error";
29126
29267
  const label = this.name ? `${this.name}: ` : "";
29127
- const suffix = /--help/.test(raw) ? "" : `
29268
+ const suffix = /--help/.test(raw2) ? "" : `
29128
29269
  Run \`${this.name ?? "cli"} --help\` for usage.`;
29129
- process52.stderr.write(`${label}${raw}${suffix}
29270
+ process52.stderr.write(`${label}${raw2}${suffix}
29130
29271
  `);
29131
29272
  process52.exit(e2.exitCode ?? 2);
29132
29273
  }
@@ -29984,7 +30125,7 @@ function getPrefix() {
29984
30125
  }
29985
30126
  var prefix = getPrefix();
29986
30127
  // package.json
29987
- var version2 = "0.1.30";
30128
+ var version2 = "0.1.32";
29988
30129
 
29989
30130
  // bin/cli.ts
29990
30131
  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))
@@ -12435,6 +12461,8 @@ function formatSubqueryValue(val) {
12435
12461
  return val ? "1" : "0";
12436
12462
  if (typeof val === "string")
12437
12463
  return `'${val.replace(/'/g, "''")}'`;
12464
+ if (val instanceof Date)
12465
+ return `'${val.toISOString()}'`;
12438
12466
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
12439
12467
  }
12440
12468
  function buildOverClause(partitionBy, orderBy) {
@@ -13091,12 +13119,12 @@ function createQueryBuilder(state) {
13091
13119
  return this;
13092
13120
  },
13093
13121
  selectRaw(fragment) {
13094
- assertSqlFragment(fragment, "selectRaw(fragment)");
13122
+ const frag = renderRawFragment(fragment, "selectRaw(fragment)");
13095
13123
  const fromIdx = text.indexOf(" FROM ");
13096
13124
  if (fromIdx !== -1) {
13097
- text = `${text.substring(0, fromIdx)}, ${String(fragment)}${text.substring(fromIdx)}`;
13125
+ text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
13098
13126
  } else {
13099
- text += `, ${String(fragment)}`;
13127
+ text += `, ${frag}`;
13100
13128
  }
13101
13129
  built = null;
13102
13130
  return this;
@@ -13209,12 +13237,15 @@ function createQueryBuilder(state) {
13209
13237
  if (cols.length === 0)
13210
13238
  return this;
13211
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] : "";
13212
13242
  const fromIndex = text.indexOf(" FROM ");
13213
13243
  if (fromIndex !== -1) {
13214
- text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
13244
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
13215
13245
  } else {
13216
- text = `SELECT ${rendered.join(", ")} FROM ${table}`;
13246
+ text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
13217
13247
  }
13248
+ built = null;
13218
13249
  return this;
13219
13250
  },
13220
13251
  addSelect(...columns2) {
@@ -13292,6 +13323,54 @@ function createQueryBuilder(state) {
13292
13323
  }
13293
13324
  return "";
13294
13325
  };
13326
+ const buildJoinConstraint = (targetTbl) => {
13327
+ if (!condition)
13328
+ return "";
13329
+ const frags = [];
13330
+ const addCmp = (col, op, val) => {
13331
+ validateIdentifier(String(col), "with() constraint column");
13332
+ const operator = assertSafeWhereOperator(op, "with() constraint operator");
13333
+ frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
13334
+ };
13335
+ const unsupported = (m) => () => {
13336
+ throw new Error(`[query-builder] with('${relationKey}', ...): ${m} is not supported inside a constraint callback on the JOIN-based builder \u2014 apply it to the outer query, or use the model layer's eager loading. (Silently ignoring it would return wrong data.)`);
13337
+ };
13338
+ const constraintQb = {
13339
+ where: (expr, op, val) => {
13340
+ if (Array.isArray(expr))
13341
+ addCmp(expr[0], expr[1], expr[2]);
13342
+ else if (expr && typeof expr === "object")
13343
+ for (const k of Object.keys(expr))
13344
+ addCmp(k, "=", expr[k]);
13345
+ else if (op !== undefined && val !== undefined)
13346
+ addCmp(expr, op, val);
13347
+ else if (op !== undefined)
13348
+ addCmp(expr, "=", op);
13349
+ return constraintQb;
13350
+ },
13351
+ whereIn: (col, vals) => {
13352
+ validateIdentifier(String(col), "with() constraint column");
13353
+ frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
13354
+ return constraintQb;
13355
+ },
13356
+ whereNull: (col) => {
13357
+ validateIdentifier(String(col), "with() constraint column");
13358
+ frags.push(`${targetTbl}.${String(col)} IS NULL`);
13359
+ return constraintQb;
13360
+ },
13361
+ whereNotNull: (col) => {
13362
+ validateIdentifier(String(col), "with() constraint column");
13363
+ frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
13364
+ return constraintQb;
13365
+ },
13366
+ orderBy: unsupported("orderBy()"),
13367
+ limit: unsupported("limit()"),
13368
+ offset: unsupported("offset()"),
13369
+ take: unsupported("take()")
13370
+ };
13371
+ condition(constraintQb);
13372
+ return frags.length ? ` AND ${frags.join(" AND ")}` : "";
13373
+ };
13295
13374
  const resolveTarget = () => {
13296
13375
  const pick = (m) => {
13297
13376
  const modelName = m?.[relationKey];
@@ -13336,7 +13415,7 @@ function createQueryBuilder(state) {
13336
13415
  const throughPk = meta.primaryKeys[throughTable] ?? "id";
13337
13416
  const fkInThrough = `${singularize(fromTable)}_id`;
13338
13417
  const fkInFinal = `${singularize(throughTable)}_id`;
13339
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(throughTable)} ON ${sql(`${throughTable}.${fkInThrough}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(finalTable)} ON ${sql(`${finalTable}.${fkInFinal}`)} = ${sql(`${throughTable}.${throughPk}`)}`;
13418
+ insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
13340
13419
  joinedTables.add(throughTable);
13341
13420
  joinedTables.add(finalTable);
13342
13421
  return finalTable;
@@ -13349,7 +13428,7 @@ function createQueryBuilder(state) {
13349
13428
  const childPk = meta.primaryKeys[childTable] ?? "id";
13350
13429
  const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
13351
13430
  const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
13352
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivot)} ON ${sql(`${pivot}.${fkA}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${childPk}`)} = ${sql(`${pivot}.${fkB}`)}`;
13431
+ insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
13353
13432
  joinedTables.add(pivot);
13354
13433
  joinedTables.add(childTable);
13355
13434
  return childTable;
@@ -13363,7 +13442,8 @@ function createQueryBuilder(state) {
13363
13442
  const morphType = `${morphName}_type`;
13364
13443
  const morphId = `${morphName}_id`;
13365
13444
  const targetFk = `${singularize(childTable)}_id`;
13366
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivotTable)} ON ${sql(`${pivotTable}.${morphId}`)} = ${sql(`${fromTable}.${fromPk}`)} AND ${sql(`${pivotTable}.${morphType}`)} = ${sql(meta.tableToModel[fromTable] || fromTable)} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${childPk}`)} = ${sql(`${pivotTable}.${targetFk}`)}`;
13445
+ const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
13446
+ insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
13367
13447
  joinedTables.add(pivotTable);
13368
13448
  joinedTables.add(childTable);
13369
13449
  return childTable;
@@ -13379,7 +13459,8 @@ function createQueryBuilder(state) {
13379
13459
  const morphType = `${morphName}_type`;
13380
13460
  const morphId = `${morphName}_id`;
13381
13461
  const relatedFk = `${singularize(relatedTable)}_id`;
13382
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(pivotTable)} ON ${sql(`${pivotTable}.${relatedFk}`)} = ${sql(`${fromTable}.${fromPk}`)} LEFT JOIN ${sql(relatedTable)} ON ${sql(`${relatedTable}.${relatedPk}`)} = ${sql(`${pivotTable}.${morphId}`)} AND ${sql(`${pivotTable}.${morphType}`)} = ${sql(meta.tableToModel[relatedTable] || relatedTable)}`;
13462
+ const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
13463
+ insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
13383
13464
  joinedTables.add(pivotTable);
13384
13465
  joinedTables.add(relatedTable);
13385
13466
  return relatedTable;
@@ -13388,7 +13469,7 @@ function createQueryBuilder(state) {
13388
13469
  if (isBt) {
13389
13470
  const fkInParent = `${singularize(childTable)}_id`;
13390
13471
  const childPk = meta.primaryKeys[childTable] ?? "id";
13391
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${fromTable}.${fkInParent}`)} = ${sql(`${childTable}.${childPk}`)}`;
13472
+ insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
13392
13473
  joinedTables.add(childTable);
13393
13474
  return childTable;
13394
13475
  }
@@ -13398,20 +13479,15 @@ function createQueryBuilder(state) {
13398
13479
  const morphType = `${relationKey}_type`;
13399
13480
  const morphId = `${relationKey}_id`;
13400
13481
  const fromPk = meta.primaryKeys[fromTable] ?? "id";
13401
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${morphId}`)} = ${sql(`${fromTable}.${fromPk}`)} AND ${sql(`${childTable}.${morphType}`)} = ${sql(meta.tableToModel[fromTable] || fromTable)}`;
13482
+ const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
13483
+ insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
13402
13484
  joinedTables.add(childTable);
13403
13485
  return childTable;
13404
13486
  }
13405
13487
  const fkInChild = `${singularize(fromTable)}_id`;
13406
13488
  const pk = meta.primaryKeys[fromTable] ?? "id";
13407
- const softDeleteCheck = addSoftDeleteCheck(childTable);
13408
- if (softDeleteCheck) {
13409
- const currentSql = String(ensureBuilt());
13410
- const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
13411
- built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
13412
- } else {
13413
- built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
13414
- }
13489
+ const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
13490
+ insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
13415
13491
  joinedTables.add(childTable);
13416
13492
  return childTable;
13417
13493
  };
@@ -13448,7 +13524,7 @@ function createQueryBuilder(state) {
13448
13524
  addToSelectClause(pivotColumnsStr);
13449
13525
  }
13450
13526
  }
13451
- text = computeSqlText(ensureBuilt());
13527
+ built = null;
13452
13528
  return this;
13453
13529
  },
13454
13530
  whereHas(relation, callback) {
@@ -14058,9 +14134,9 @@ function createQueryBuilder(state) {
14058
14134
  return this;
14059
14135
  },
14060
14136
  whereRaw(fragment) {
14061
- assertSqlFragment(fragment, "whereRaw(fragment)");
14137
+ const frag = renderRawFragment(fragment, "whereRaw(fragment)");
14062
14138
  const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
14063
- text += ` ${keyword} ${String(fragment)}`;
14139
+ text += ` ${keyword} ${frag}`;
14064
14140
  built = null;
14065
14141
  return this;
14066
14142
  },
@@ -14426,8 +14502,8 @@ function createQueryBuilder(state) {
14426
14502
  return this;
14427
14503
  },
14428
14504
  groupByRaw(fragment) {
14429
- assertSqlFragment(fragment, "groupByRaw(fragment)");
14430
- text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} GROUP BY ${String(fragment)}`;
14505
+ const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
14506
+ text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
14431
14507
  built = null;
14432
14508
  return this;
14433
14509
  },
@@ -14459,15 +14535,15 @@ function createQueryBuilder(state) {
14459
14535
  return this;
14460
14536
  },
14461
14537
  havingRaw(fragment) {
14462
- assertSqlFragment(fragment, "havingRaw(fragment)");
14538
+ const frag = renderRawFragment(fragment, "havingRaw(fragment)");
14463
14539
  const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
14464
- text += ` ${kw} ${String(fragment)}`;
14540
+ text += ` ${kw} ${frag}`;
14465
14541
  built = null;
14466
14542
  return this;
14467
14543
  },
14468
14544
  orderByRaw(fragment) {
14469
- assertSqlFragment(fragment, "orderByRaw(fragment)");
14470
- text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${String(fragment)}` : `${text} ORDER BY ${String(fragment)}`;
14545
+ const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
14546
+ text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
14471
14547
  built = null;
14472
14548
  return this;
14473
14549
  },
@@ -14650,20 +14726,20 @@ function createQueryBuilder(state) {
14650
14726
  includeTrashed = true;
14651
14727
  onlyTrashed = true;
14652
14728
  const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
14653
- const splice = (raw, predicate2) => {
14654
- const upper = raw.toUpperCase();
14729
+ const splice = (raw2, predicate2) => {
14730
+ const upper = raw2.toUpperCase();
14655
14731
  let depth = 0;
14656
- for (let i = 0;i < raw.length; i++) {
14657
- const c = raw[i];
14732
+ for (let i = 0;i < raw2.length; i++) {
14733
+ const c = raw2[i];
14658
14734
  if (c === "(")
14659
14735
  depth++;
14660
14736
  else if (c === ")")
14661
14737
  depth--;
14662
- else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
14663
- return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
14738
+ else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
14739
+ return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
14664
14740
  }
14665
14741
  }
14666
- return `${raw} WHERE ${predicate2}`;
14742
+ return `${raw2} WHERE ${predicate2}`;
14667
14743
  };
14668
14744
  const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
14669
14745
  text = splice(text, predicate);
@@ -14967,13 +15043,13 @@ function createQueryBuilder(state) {
14967
15043
  const cacheKey = `${String(table)}|${prop}`;
14968
15044
  let chosen = dynamicWhereColumnCache.get(cacheKey);
14969
15045
  if (chosen === undefined) {
14970
- const raw = prop.replace(/^(?:or|and)?where/i, "");
14971
- if (!raw) {
15046
+ const raw2 = prop.replace(/^(?:or|and)?where/i, "");
15047
+ if (!raw2) {
14972
15048
  dynamicWhereColumnCache.set(cacheKey, "");
14973
15049
  chosen = "";
14974
15050
  } else {
14975
- const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
14976
- const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
15051
+ const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
15052
+ const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
14977
15053
  const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
14978
15054
  chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
14979
15055
  dynamicWhereColumnCache.set(cacheKey, chosen);
@@ -15363,6 +15439,10 @@ function createQueryBuilder(state) {
15363
15439
  returning(...cols) {
15364
15440
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15365
15441
  const q = _sql.unsafe(returningSql, params);
15442
+ const runFirst = async () => {
15443
+ const rows = await runWithHooks(q, "insert");
15444
+ return Array.isArray(rows) ? rows[0] : rows;
15445
+ };
15366
15446
  return {
15367
15447
  where: () => this,
15368
15448
  andWhere: () => this,
@@ -15371,7 +15451,22 @@ function createQueryBuilder(state) {
15371
15451
  limit: () => this,
15372
15452
  offset: () => this,
15373
15453
  toSQL: () => makeExecutableQuery(q, returningSql),
15374
- execute: () => runWithHooks(q, "insert")
15454
+ execute: () => runWithHooks(q, "insert"),
15455
+ get: () => runWithHooks(q, "insert"),
15456
+ first: runFirst,
15457
+ executeTakeFirst: runFirst,
15458
+ async firstOrFail() {
15459
+ const row = await runFirst();
15460
+ if (!row)
15461
+ throw new Error("Insert with RETURNING returned no rows");
15462
+ return row;
15463
+ },
15464
+ async executeTakeFirstOrThrow() {
15465
+ const row = await runFirst();
15466
+ if (!row)
15467
+ throw new Error("Insert with RETURNING returned no rows");
15468
+ return row;
15469
+ }
15375
15470
  };
15376
15471
  },
15377
15472
  toSQL() {
@@ -15410,19 +15505,27 @@ function createQueryBuilder(state) {
15410
15505
  returningAll() {
15411
15506
  const returningSql = `${sqlText} RETURNING *`;
15412
15507
  const q = _sql.unsafe(returningSql, params);
15508
+ const runFirst = async () => {
15509
+ const result = await runWithHooks(q, "insert");
15510
+ return Array.isArray(result) ? result[0] : result;
15511
+ };
15413
15512
  return {
15414
15513
  toSQL: () => makeExecutableQuery(q, returningSql),
15415
15514
  execute: () => runWithHooks(q, "insert"),
15416
- async executeTakeFirst() {
15417
- const result = await runWithHooks(q, "insert");
15418
- return Array.isArray(result) ? result[0] : result;
15515
+ get: () => runWithHooks(q, "insert"),
15516
+ first: runFirst,
15517
+ executeTakeFirst: runFirst,
15518
+ async firstOrFail() {
15519
+ const row = await runFirst();
15520
+ if (!row)
15521
+ throw new Error("Insert with RETURNING returned no rows");
15522
+ return row;
15419
15523
  },
15420
15524
  async executeTakeFirstOrThrow() {
15421
- const result = await runWithHooks(q, "insert");
15422
- const first = Array.isArray(result) ? result[0] : result;
15423
- if (!first)
15424
- throw new Error("Insert with RETURNING failed");
15425
- return first;
15525
+ const row = await runFirst();
15526
+ if (!row)
15527
+ throw new Error("Insert with RETURNING returned no rows");
15528
+ return row;
15426
15529
  }
15427
15530
  };
15428
15531
  }
@@ -15486,6 +15589,10 @@ function createQueryBuilder(state) {
15486
15589
  returning(...cols) {
15487
15590
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15488
15591
  const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
15592
+ const runFirst = async () => {
15593
+ const rows = await runWithHooks(q, "update");
15594
+ return Array.isArray(rows) ? rows[0] : rows;
15595
+ };
15489
15596
  const obj = {
15490
15597
  where: () => obj,
15491
15598
  andWhere: () => obj,
@@ -15494,7 +15601,22 @@ function createQueryBuilder(state) {
15494
15601
  limit: () => obj,
15495
15602
  offset: () => obj,
15496
15603
  toSQL: () => makeExecutableQuery(q, retText),
15497
- execute: () => runWithHooks(q, "update")
15604
+ execute: () => runWithHooks(q, "update"),
15605
+ get: () => runWithHooks(q, "update"),
15606
+ first: runFirst,
15607
+ executeTakeFirst: runFirst,
15608
+ async firstOrFail() {
15609
+ const row = await runFirst();
15610
+ if (!row)
15611
+ throw new Error("Update with RETURNING returned no rows");
15612
+ return row;
15613
+ },
15614
+ async executeTakeFirstOrThrow() {
15615
+ const row = await runFirst();
15616
+ if (!row)
15617
+ throw new Error("Update with RETURNING returned no rows");
15618
+ return row;
15619
+ }
15498
15620
  };
15499
15621
  return obj;
15500
15622
  },
@@ -15593,6 +15715,10 @@ function createQueryBuilder(state) {
15593
15715
  returning(...cols) {
15594
15716
  const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
15595
15717
  const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
15718
+ const runFirst = async () => {
15719
+ const rows = await runWithHooks(q, "delete");
15720
+ return Array.isArray(rows) ? rows[0] : rows;
15721
+ };
15596
15722
  const obj = {
15597
15723
  where: () => obj,
15598
15724
  andWhere: () => obj,
@@ -15601,7 +15727,22 @@ function createQueryBuilder(state) {
15601
15727
  limit: () => obj,
15602
15728
  offset: () => obj,
15603
15729
  toSQL: () => makeExecutableQuery(q, retText),
15604
- execute: () => runWithHooks(q, "delete")
15730
+ execute: () => runWithHooks(q, "delete"),
15731
+ get: () => runWithHooks(q, "delete"),
15732
+ first: runFirst,
15733
+ executeTakeFirst: runFirst,
15734
+ async firstOrFail() {
15735
+ const row = await runFirst();
15736
+ if (!row)
15737
+ throw new Error("Delete with RETURNING returned no rows");
15738
+ return row;
15739
+ },
15740
+ async executeTakeFirstOrThrow() {
15741
+ const row = await runFirst();
15742
+ if (!row)
15743
+ throw new Error("Delete with RETURNING returned no rows");
15744
+ return row;
15745
+ }
15605
15746
  };
15606
15747
  return obj;
15607
15748
  },
@@ -17953,8 +18094,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
17953
18094
  return;
17954
18095
  }
17955
18096
  try {
17956
- const raw = readFileSync3(snapshotPath, "utf8");
17957
- const parsed = JSON.parse(raw);
18097
+ const raw2 = readFileSync3(snapshotPath, "utf8");
18098
+ const parsed = JSON.parse(raw2);
17958
18099
  if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
17959
18100
  return parsed.plan;
17960
18101
  }
@@ -18013,8 +18154,8 @@ async function generateMigration(dir, opts = {}) {
18013
18154
  const statePath = String(opts.state || defaultStatePath);
18014
18155
  if (existsSync16(statePath)) {
18015
18156
  try {
18016
- const raw = readFileSync3(statePath, "utf8");
18017
- const parsed = JSON.parse(raw);
18157
+ const raw2 = readFileSync3(statePath, "utf8");
18158
+ const parsed = JSON.parse(raw2);
18018
18159
  previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
18019
18160
  if (previous) {
18020
18161
  info("-- Comparing with legacy state file (will migrate to new snapshot format)");
@@ -24604,10 +24745,10 @@ class BelongsToManyRelationBuilder {
24604
24745
  hydrateRows(rows) {
24605
24746
  const fkParent = this.fkParent;
24606
24747
  const fkRelated = this.fkRelated;
24607
- return rows.map((raw) => {
24748
+ return rows.map((raw2) => {
24608
24749
  const relatedRow = {};
24609
24750
  const pivotExtras = {};
24610
- for (const [k2, v2] of Object.entries(raw)) {
24751
+ for (const [k2, v2] of Object.entries(raw2)) {
24611
24752
  if (k2.startsWith(BTM_RELATED_ALIAS))
24612
24753
  relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
24613
24754
  else if (k2 !== fkParent && k2 !== fkRelated)
@@ -27974,6 +28115,7 @@ export {
27974
28115
  relationDiagram,
27975
28116
  registerModel,
27976
28117
  registerBrowserModels,
28118
+ raw,
27977
28119
  queryExplainAll,
27978
28120
  ping,
27979
28121
  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.30",
4
+ "version": "0.1.32",
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",