bun-query-builder 0.2.25 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -11644,6 +11644,29 @@ import process19 from "process";
11644
11644
  function isMysqlLike(dialect = config5.dialect) {
11645
11645
  return dialect === "mysql" || dialect === "singlestore" || dialect === "vitess";
11646
11646
  }
11647
+ function isPlainObject(value) {
11648
+ if (value === null || typeof value !== "object" || Array.isArray(value))
11649
+ return false;
11650
+ const proto = Object.getPrototypeOf(value);
11651
+ return proto === Object.prototype || proto === null;
11652
+ }
11653
+ function mergeObject(target, source, seen = new Set) {
11654
+ const out = { ...target };
11655
+ seen.add(source);
11656
+ for (const key of Object.keys(source)) {
11657
+ if (FORBIDDEN_MERGE_KEYS.has(key))
11658
+ continue;
11659
+ const next = source[key];
11660
+ if (isPlainObject(next) && !seen.has(next)) {
11661
+ const prev = out[key];
11662
+ out[key] = mergeObject(isPlainObject(prev) ? prev : {}, next, seen);
11663
+ } else {
11664
+ out[key] = next;
11665
+ }
11666
+ }
11667
+ seen.delete(source);
11668
+ return out;
11669
+ }
11647
11670
  function resolveDialect(explicit) {
11648
11671
  if (explicit)
11649
11672
  return explicit;
@@ -11687,21 +11710,12 @@ async function getConfig() {
11687
11710
  return config5;
11688
11711
  }
11689
11712
  function applyConfig(userConfig) {
11690
- Object.assign(config5, userConfig);
11691
- if (userConfig.database) {
11692
- config5.database = { ...config5.database, ...userConfig.database };
11693
- }
11694
- if (userConfig.timestamps) {
11695
- config5.timestamps = { ...config5.timestamps, ...userConfig.timestamps };
11696
- }
11697
- if (userConfig.pagination) {
11698
- config5.pagination = { ...config5.pagination, ...userConfig.pagination };
11699
- }
11700
- if (userConfig.softDeletes) {
11701
- config5.softDeletes = { ...config5.softDeletes, ...userConfig.softDeletes };
11702
- }
11703
- if (userConfig.vitess) {
11704
- config5.vitess = { ...config5.vitess, ...userConfig.vitess };
11713
+ const merged = mergeObject(config5, userConfig);
11714
+ const target = config5;
11715
+ for (const key of Object.keys(merged)) {
11716
+ if (FORBIDDEN_MERGE_KEYS.has(key))
11717
+ continue;
11718
+ target[key] = merged[key];
11705
11719
  }
11706
11720
  }
11707
11721
  function setConfig(userConfig) {
@@ -11709,7 +11723,7 @@ function setConfig(userConfig) {
11709
11723
  const key = `${_lastConfiguredDialect}->${userConfig.dialect}`;
11710
11724
  if (!_warnedDialectConflicts.has(key)) {
11711
11725
  _warnedDialectConflicts.add(key);
11712
- console.warn(`[query-builder] setConfig({ dialect: '${userConfig.dialect}' }) overrides a previous ` + `setConfig({ dialect: '${_lastConfiguredDialect}' }). ` + `Config is process-wide; in-flight queries from the previous configuration may break. ` + `Run conflicting connections in separate processes \u2014 see stacksjs/stacks#1862 #18.`);
11726
+ console.warn(`[query-builder] Configuring dialect '${userConfig.dialect}' overrides the previously ` + `configured dialect '${_lastConfiguredDialect}'. ` + `Config is process-wide (setConfig, db.configure and query-builder.config.ts all write it); ` + `in-flight queries from the previous configuration may break. ` + `Run conflicting connections in separate processes \u2014 see stacksjs/stacks#1862 #18.`);
11713
11727
  }
11714
11728
  }
11715
11729
  if (userConfig.dialect)
@@ -11718,7 +11732,10 @@ function setConfig(userConfig) {
11718
11732
  configState.explicit.add(key);
11719
11733
  applyConfig(userConfig);
11720
11734
  }
11721
- var defaultConfig4, CONFIG_SINGLETON_KEY, config5, CONFIG_STATE_KEY, configState, _lastConfiguredDialect = null, _warnedDialectConflicts;
11735
+ function defineConfig(userConfig) {
11736
+ return userConfig;
11737
+ }
11738
+ var defaultConfig4, FORBIDDEN_MERGE_KEYS, CONFIG_SINGLETON_KEY, config5, CONFIG_STATE_KEY, configState, _lastConfiguredDialect = null, _warnedDialectConflicts;
11722
11739
  var init_config = __esm(() => {
11723
11740
  init_dist();
11724
11741
  defaultConfig4 = {
@@ -11783,8 +11800,9 @@ var init_config = __esm(() => {
11783
11800
  defaultFilter: true
11784
11801
  }
11785
11802
  };
11803
+ FORBIDDEN_MERGE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
11786
11804
  CONFIG_SINGLETON_KEY = Symbol.for("bun-query-builder.config");
11787
- config5 = globalThis[CONFIG_SINGLETON_KEY] ??= { ...defaultConfig4 };
11805
+ config5 = globalThis[CONFIG_SINGLETON_KEY] ??= mergeObject({}, defaultConfig4);
11788
11806
  CONFIG_STATE_KEY = Symbol.for("bun-query-builder.config-state");
11789
11807
  configState = globalThis[CONFIG_STATE_KEY] ??= { explicit: new Set, fileLoaded: false };
11790
11808
  _warnedDialectConflicts = new Set;
@@ -12827,6 +12845,20 @@ function createQueryBuilder(state) {
12827
12845
  const _sql = state?.sql ?? getOrCreateBunSql();
12828
12846
  const meta = state?.meta;
12829
12847
  const schema = state?.schema;
12848
+ function advisoryLockKey(key) {
12849
+ if (typeof key === "number")
12850
+ return key;
12851
+ const s = String(key);
12852
+ let hash = 7;
12853
+ for (let i = 0;i < s.length; i++)
12854
+ hash = hash * 31 + s.charCodeAt(i) | 0;
12855
+ return Math.abs(hash);
12856
+ }
12857
+ function activeHooks() {
12858
+ if (!state?.hooks)
12859
+ return config5.hooks;
12860
+ return { ...config5.hooks, ...state.hooks };
12861
+ }
12830
12862
  const dynamicWhereColumnCache = new Map;
12831
12863
  function applyCondition(expr) {
12832
12864
  if (Array.isArray(expr)) {
@@ -12918,7 +12950,7 @@ function createQueryBuilder(state) {
12918
12950
  return;
12919
12951
  }
12920
12952
  function runWithHooks(q, kind, opts) {
12921
- const hooks = config5.hooks;
12953
+ const hooks = activeHooks();
12922
12954
  const slowMs = hooks?.slowQueryThresholdMs;
12923
12955
  const slowEnabled = slowMs != null && slowMs >= 0;
12924
12956
  const hasSlowQuery = Boolean(hooks?.onSlowQuery || slowEnabled);
@@ -15000,7 +15032,7 @@ function createQueryBuilder(state) {
15000
15032
  return text;
15001
15033
  },
15002
15034
  async get() {
15003
- const hooks = config5.hooks;
15035
+ const hooks = activeHooks();
15004
15036
  const hasQueryHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hasSlowQueryHook(hooks));
15005
15037
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !hasQueryHooks) {
15006
15038
  const prepareFn = _sql._prepareStatement;
@@ -15053,7 +15085,7 @@ function createQueryBuilder(state) {
15053
15085
  return result;
15054
15086
  },
15055
15087
  async first() {
15056
- const fHooks = config5.hooks;
15088
+ const fHooks = activeHooks();
15057
15089
  const fHasQueryHooks = fHooks && (fHooks.onQueryStart || fHooks.onQueryEnd || fHooks.onQueryError || fHooks.startSpan || hasSlowQueryHook(fHooks));
15058
15090
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !fHasQueryHooks) {
15059
15091
  const prepareFn = _sql._prepareStatement;
@@ -15135,7 +15167,7 @@ function createQueryBuilder(state) {
15135
15167
  } else {
15136
15168
  countText = `SELECT COUNT(*) as c FROM ${table}`;
15137
15169
  }
15138
- const cHooks = config5.hooks;
15170
+ const cHooks = activeHooks();
15139
15171
  const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan || hasSlowQueryHook(cHooks));
15140
15172
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
15141
15173
  const prepareFn = _sql._prepareStatement;
@@ -15153,7 +15185,7 @@ function createQueryBuilder(state) {
15153
15185
  async avg(column) {
15154
15186
  const fromIdx = text.indexOf(" FROM ");
15155
15187
  const avgText = fromIdx !== -1 ? `SELECT AVG(${column}) as a${text.substring(fromIdx)}` : `SELECT AVG(${column}) as a FROM ${table}`;
15156
- const aHooks = config5.hooks;
15188
+ const aHooks = activeHooks();
15157
15189
  const aHasHooks = aHooks && (aHooks.onQueryStart || aHooks.onQueryEnd || aHooks.onQueryError || aHooks.startSpan || hasSlowQueryHook(aHooks));
15158
15190
  if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !aHasHooks) {
15159
15191
  const prepareFn = _sql._prepareStatement;
@@ -15290,7 +15322,7 @@ function createQueryBuilder(state) {
15290
15322
  }
15291
15323
  return {
15292
15324
  configure(opts) {
15293
- Object.assign(config5, opts);
15325
+ setConfig(opts);
15294
15326
  return this;
15295
15327
  },
15296
15328
  id(name) {
@@ -15667,7 +15699,7 @@ function createQueryBuilder(state) {
15667
15699
  return makeExecutableQuery(built, sqlText);
15668
15700
  },
15669
15701
  execute() {
15670
- const hooks = config5.hooks;
15702
+ const hooks = activeHooks();
15671
15703
  const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15672
15704
  if (!hasHooks) {
15673
15705
  const prepareFn = _sql._prepareStatement;
@@ -16021,13 +16053,13 @@ function createQueryBuilder(state) {
16021
16053
  },
16022
16054
  async execute() {
16023
16055
  try {
16024
- await config5.hooks?.beforeDelete?.({ table: String(table), where: whereCondition });
16056
+ await activeHooks()?.beforeDelete?.({ table: String(table), where: whereCondition });
16025
16057
  } catch (err) {
16026
16058
  throw err;
16027
16059
  }
16028
16060
  const result = mutationCount(await runWithHooks(ensureDelBuilt(), "delete"));
16029
16061
  try {
16030
- await config5.hooks?.afterDelete?.({ table: String(table), where: whereCondition, result });
16062
+ await activeHooks()?.afterDelete?.({ table: String(table), where: whereCondition, result });
16031
16063
  } catch {}
16032
16064
  return result;
16033
16065
  },
@@ -16091,18 +16123,14 @@ function createQueryBuilder(state) {
16091
16123
  },
16092
16124
  async advisoryLock(key) {
16093
16125
  if (config5.dialect === "postgres") {
16094
- const s = String(key);
16095
- let hash = 7;
16096
- for (let i = 0;i < s.length; i++)
16097
- hash = hash * 31 + s.charCodeAt(i) | 0;
16098
- const k = typeof key === "number" ? key : Math.abs(hash);
16099
- const q = bunSql`SELECT pg_advisory_lock(${k})`;
16126
+ const k = advisoryLockKey(key);
16127
+ const q = _sql`SELECT pg_advisory_lock(${k})`;
16100
16128
  await runWithHooks(q, "raw");
16101
16129
  return;
16102
16130
  }
16103
16131
  if (isMysqlLike(config5.dialect)) {
16104
16132
  const lockName = `bqb:${String(key)}`;
16105
- const q = bunSql`SELECT GET_LOCK(${lockName}, -1) AS ok`;
16133
+ const q = _sql`SELECT GET_LOCK(${lockName}, -1) AS ok`;
16106
16134
  await runWithHooks(q, "raw");
16107
16135
  return;
16108
16136
  }
@@ -16110,18 +16138,29 @@ function createQueryBuilder(state) {
16110
16138
  },
16111
16139
  async tryAdvisoryLock(key) {
16112
16140
  if (config5.dialect === "postgres") {
16113
- const s = String(key);
16114
- let hash = 7;
16115
- for (let i = 0;i < s.length; i++)
16116
- hash = hash * 31 + s.charCodeAt(i) | 0;
16117
- const k = typeof key === "number" ? key : Math.abs(hash);
16118
- const q = bunSql`SELECT pg_try_advisory_lock(${k}) as ok`;
16141
+ const k = advisoryLockKey(key);
16142
+ const q = _sql`SELECT pg_try_advisory_lock(${k}) as ok`;
16143
+ const rows = await runWithHooks(q, "raw");
16144
+ return Boolean(rows?.[0]?.ok);
16145
+ }
16146
+ if (isMysqlLike(config5.dialect)) {
16147
+ const lockName = `bqb:${String(key)}`;
16148
+ const q = _sql`SELECT GET_LOCK(${lockName}, 0) AS ok`;
16149
+ const rows = await runWithHooks(q, "raw");
16150
+ return Number(rows?.[0]?.ok) === 1;
16151
+ }
16152
+ return false;
16153
+ },
16154
+ async advisoryUnlock(key) {
16155
+ if (config5.dialect === "postgres") {
16156
+ const k = advisoryLockKey(key);
16157
+ const q = _sql`SELECT pg_advisory_unlock(${k}) as ok`;
16119
16158
  const rows = await runWithHooks(q, "raw");
16120
16159
  return Boolean(rows?.[0]?.ok);
16121
16160
  }
16122
16161
  if (isMysqlLike(config5.dialect)) {
16123
16162
  const lockName = `bqb:${String(key)}`;
16124
- const q = bunSql`SELECT GET_LOCK(${lockName}, 0) AS ok`;
16163
+ const q = _sql`SELECT RELEASE_LOCK(${lockName}) AS ok`;
16125
16164
  const rows = await runWithHooks(q, "raw");
16126
16165
  return Number(rows?.[0]?.ok) === 1;
16127
16166
  }
@@ -16131,11 +16170,11 @@ function createQueryBuilder(state) {
16131
16170
  return _sql.unsafe(query, params);
16132
16171
  },
16133
16172
  file(path, params) {
16134
- return bunSql.file(path, params);
16173
+ return _sql.file(path, params);
16135
16174
  },
16136
16175
  async reserve() {
16137
16176
  const reserved = await bunSql.reserve();
16138
- const qb = createQueryBuilder({ sql: reserved, meta, schema });
16177
+ const qb = createQueryBuilder({ sql: reserved, meta, schema, hooks: state?.hooks });
16139
16178
  qb.release = () => reserved.release();
16140
16179
  return qb;
16141
16180
  },
@@ -16155,7 +16194,7 @@ function createQueryBuilder(state) {
16155
16194
  },
16156
16195
  async ping() {
16157
16196
  try {
16158
- const q = bunSql`SELECT 1`;
16197
+ const q = _sql`SELECT 1`;
16159
16198
  await runWithHooks(q, "select");
16160
16199
  return true;
16161
16200
  } catch {
@@ -16182,7 +16221,7 @@ function createQueryBuilder(state) {
16182
16221
  opts.logger?.({ type: "start", attempt: attempt2 });
16183
16222
  const start = Date.now();
16184
16223
  return await txConn[txMethod](async (tx) => {
16185
- const qb = createQueryBuilder({ sql: tx, meta, schema, inTransaction: true });
16224
+ const qb = createQueryBuilder({ sql: tx, meta, schema, hooks: state?.hooks, inTransaction: true });
16186
16225
  if (opts?.isolation && !nested) {
16187
16226
  const level = opts.isolation;
16188
16227
  const upper = level === "read committed" ? "READ COMMITTED" : level === "repeatable read" ? "REPEATABLE READ" : "SERIALIZABLE";
@@ -16245,14 +16284,14 @@ function createQueryBuilder(state) {
16245
16284
  if (!s || typeof s.savepoint !== "function")
16246
16285
  throw new Error("savepoint() is not supported by the active connection");
16247
16286
  return await s.savepoint(async (sp) => {
16248
- const qb = createQueryBuilder({ sql: sp, meta, schema, inTransaction: true });
16287
+ const qb = createQueryBuilder({ sql: sp, meta, schema, hooks: state?.hooks, inTransaction: true });
16249
16288
  return await fn(qb);
16250
16289
  });
16251
16290
  },
16252
16291
  async beginDistributed(name, fn) {
16253
16292
  const txConn = state?.sql ?? bunSql;
16254
16293
  const res = await txConn.beginDistributed(name, async (tx) => {
16255
- const qb = createQueryBuilder({ sql: tx, meta, schema });
16294
+ const qb = createQueryBuilder({ sql: tx, meta, schema, hooks: state?.hooks });
16256
16295
  return await fn(qb);
16257
16296
  });
16258
16297
  return res;
@@ -16278,24 +16317,24 @@ function createQueryBuilder(state) {
16278
16317
  const { colsSql, valuesSql, params } = buildInsertClause(rows);
16279
16318
  const tbl = quoteInsertIdent(String(table));
16280
16319
  const sqlText = isMysqlLike(config5.dialect) ? `INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}` : `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} ON CONFLICT DO NOTHING`;
16281
- return bunSql.unsafe(sqlText, params).execute();
16320
+ return _sql.unsafe(sqlText, params).execute();
16282
16321
  },
16283
16322
  async insertGetId(table, values, idColumn = "id") {
16284
16323
  const { colsSql, valuesSql, params } = buildInsertClause([values]);
16285
16324
  const tbl = quoteInsertIdent(String(table));
16286
16325
  if (isMysqlLike(config5.dialect)) {
16287
- await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16288
- const [row2] = await bunSql.unsafe(`SELECT LAST_INSERT_ID() as id`).execute();
16326
+ await _sql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16327
+ const [row2] = await _sql.unsafe(`SELECT LAST_INSERT_ID() as id`).execute();
16289
16328
  return row2?.id;
16290
16329
  }
16291
16330
  if (config5.dialect === "sqlite") {
16292
- const res = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16331
+ const res = await _sql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16293
16332
  if (res?.lastInsertRowid != null)
16294
16333
  return res.lastInsertRowid;
16295
- const [row2] = await bunSql.unsafe(`SELECT last_insert_rowid() as id`).execute();
16334
+ const [row2] = await _sql.unsafe(`SELECT last_insert_rowid() as id`).execute();
16296
16335
  return row2?.id;
16297
16336
  }
16298
- const [row] = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} RETURNING ${quoteInsertIdent(String(idColumn))} as id`, params).execute();
16337
+ const [row] = await _sql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} RETURNING ${quoteInsertIdent(String(idColumn))} as id`, params).execute();
16299
16338
  return row?.id;
16300
16339
  },
16301
16340
  async updateOrInsert(table, match, values) {
@@ -16304,18 +16343,18 @@ function createQueryBuilder(state) {
16304
16343
  let idx = 1;
16305
16344
  const whereSql = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(idx++)}`).join(" AND ");
16306
16345
  const whereParams = matchKeys.map((k) => match[k]);
16307
- const existsRows = await bunSql.unsafe(`SELECT 1 FROM ${tbl} WHERE ${whereSql} LIMIT 1`, whereParams).execute();
16346
+ const existsRows = await _sql.unsafe(`SELECT 1 FROM ${tbl} WHERE ${whereSql} LIMIT 1`, whereParams).execute();
16308
16347
  if (existsRows.length) {
16309
16348
  const setKeys = Object.keys(values);
16310
16349
  let i = 1;
16311
16350
  const setSql = setKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(", ");
16312
16351
  const whereSql2 = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(" AND ");
16313
16352
  const params2 = [...setKeys.map((k) => values[k]), ...matchKeys.map((k) => match[k])];
16314
- await bunSql.unsafe(`UPDATE ${tbl} SET ${setSql} WHERE ${whereSql2}`, params2).execute();
16353
+ await _sql.unsafe(`UPDATE ${tbl} SET ${setSql} WHERE ${whereSql2}`, params2).execute();
16315
16354
  return true;
16316
16355
  }
16317
16356
  const { colsSql, valuesSql, params } = buildInsertClause([{ ...match, ...values }]);
16318
- await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16357
+ await _sql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16319
16358
  return true;
16320
16359
  },
16321
16360
  async upsert(table, rows, conflictColumns, mergeColumns) {
@@ -16329,15 +16368,15 @@ function createQueryBuilder(state) {
16329
16368
  const insert = `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`;
16330
16369
  if (isMysqlLike(config5.dialect)) {
16331
16370
  if (setCols.length === 0)
16332
- return bunSql.unsafe(`INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16371
+ return _sql.unsafe(`INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
16333
16372
  const updateList2 = setCols.map((c) => `${quoteInsertIdent(c)} = VALUES(${quoteInsertIdent(c)})`).join(", ");
16334
- return bunSql.unsafe(`${insert} ON DUPLICATE KEY UPDATE ${updateList2}`, params).execute();
16373
+ return _sql.unsafe(`${insert} ON DUPLICATE KEY UPDATE ${updateList2}`, params).execute();
16335
16374
  }
16336
16375
  const targets = targetCols.map(quoteInsertIdent).join(", ");
16337
16376
  if (setCols.length === 0)
16338
- return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO NOTHING`, params).execute();
16377
+ return _sql.unsafe(`${insert} ON CONFLICT (${targets}) DO NOTHING`, params).execute();
16339
16378
  const updateList = setCols.map((c) => `${quoteInsertIdent(c)} = EXCLUDED.${quoteInsertIdent(c)}`).join(", ");
16340
- return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO UPDATE SET ${updateList}`, params).execute();
16379
+ return _sql.unsafe(`${insert} ON CONFLICT (${targets}) DO UPDATE SET ${updateList}`, params).execute();
16341
16380
  },
16342
16381
  async save(table, values) {
16343
16382
  const pk = meta?.primaryKeys[String(table)] ?? "id";
@@ -16380,26 +16419,25 @@ function createQueryBuilder(state) {
16380
16419
  async rawQuery(query) {
16381
16420
  const start = Date.now();
16382
16421
  try {
16383
- config5.hooks?.onQueryStart?.({ sql: query, kind: "raw" });
16384
- const res = await bunSql.unsafe(query);
16385
- config5.hooks?.onQueryEnd?.({ sql: query, durationMs: Date.now() - start, kind: "raw" });
16422
+ activeHooks()?.onQueryStart?.({ sql: query, kind: "raw" });
16423
+ const res = await _sql.unsafe(query);
16424
+ activeHooks()?.onQueryEnd?.({ sql: query, durationMs: Date.now() - start, kind: "raw" });
16386
16425
  return res;
16387
16426
  } catch (err) {
16388
- config5.hooks?.onQueryError?.({ sql: query, error: err, durationMs: Date.now() - start, kind: "raw" });
16427
+ activeHooks()?.onQueryError?.({ sql: query, error: err, durationMs: Date.now() - start, kind: "raw" });
16389
16428
  throw err;
16390
16429
  }
16391
16430
  },
16392
16431
  async create(table, values) {
16393
16432
  const pk = meta?.primaryKeys[String(table)] ?? "id";
16394
16433
  try {
16395
- await config5.hooks?.beforeCreate?.({ table: String(table), data: values });
16434
+ await activeHooks()?.beforeCreate?.({ table: String(table), data: values });
16396
16435
  } catch (err) {
16397
16436
  throw err;
16398
16437
  }
16399
16438
  if (config5.dialect === "postgres") {
16400
- const q = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} RETURNING ${bunSql(String(pk))} as id`;
16439
+ const q = _sql`INSERT INTO ${_sql(String(table))} ${_sql(values)} RETURNING ${_sql(String(pk))} as id`;
16401
16440
  const [result] = await q.execute();
16402
- console.log("resultId", result);
16403
16441
  if (!result?.id) {
16404
16442
  console.error(`create() failed to get insert ID for table ${String(table)}`);
16405
16443
  console.error("Inserted values:", values);
@@ -16412,7 +16450,7 @@ function createQueryBuilder(state) {
16412
16450
  throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${result.id}`);
16413
16451
  }
16414
16452
  try {
16415
- await config5.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
16453
+ await activeHooks()?.afterCreate?.({ table: String(table), data: values, result: row });
16416
16454
  } catch {}
16417
16455
  return row;
16418
16456
  } else {
@@ -16427,7 +16465,7 @@ function createQueryBuilder(state) {
16427
16465
  throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${id}`);
16428
16466
  }
16429
16467
  try {
16430
- await config5.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
16468
+ await activeHooks()?.afterCreate?.({ table: String(table), data: values, result: row });
16431
16469
  } catch {}
16432
16470
  return row;
16433
16471
  }
@@ -16567,28 +16605,28 @@ function createQueryBuilder(state) {
16567
16605
  return await this.create(table, { ...match, ...values });
16568
16606
  },
16569
16607
  async count(table, column) {
16570
- const col = column ? bunSql(String(column)) : bunSql`*`;
16571
- const q = bunSql`SELECT COUNT(${col}) as c FROM ${bunSql(String(table))}`;
16608
+ const col = column ? _sql(String(column)) : _sql`*`;
16609
+ const q = _sql`SELECT COUNT(${col}) as c FROM ${_sql(String(table))}`;
16572
16610
  const [row] = await q.execute();
16573
16611
  return Number(row?.c ?? 0);
16574
16612
  },
16575
16613
  async sum(table, column) {
16576
- const q = bunSql`SELECT SUM(${bunSql(String(column))}) as s FROM ${bunSql(String(table))}`;
16614
+ const q = _sql`SELECT SUM(${_sql(String(column))}) as s FROM ${_sql(String(table))}`;
16577
16615
  const [row] = await q.execute();
16578
16616
  return Number(row?.s ?? 0);
16579
16617
  },
16580
16618
  async avg(table, column) {
16581
- const q = bunSql`SELECT AVG(${bunSql(String(column))}) as a FROM ${bunSql(String(table))}`;
16619
+ const q = _sql`SELECT AVG(${_sql(String(column))}) as a FROM ${_sql(String(table))}`;
16582
16620
  const [row] = await q.execute();
16583
16621
  return Number(row?.a ?? 0);
16584
16622
  },
16585
16623
  async min(table, column) {
16586
- const q = bunSql`SELECT MIN(${bunSql(String(column))}) as m FROM ${bunSql(String(table))}`;
16624
+ const q = _sql`SELECT MIN(${_sql(String(column))}) as m FROM ${_sql(String(table))}`;
16587
16625
  const [row] = await q.execute();
16588
16626
  return row?.m;
16589
16627
  },
16590
16628
  async max(table, column) {
16591
- const q = bunSql`SELECT MAX(${bunSql(String(column))}) as m FROM ${bunSql(String(table))}`;
16629
+ const q = _sql`SELECT MAX(${_sql(String(column))}) as m FROM ${_sql(String(table))}`;
16592
16630
  const [row] = await q.execute();
16593
16631
  return row?.m;
16594
16632
  },
@@ -19113,6 +19151,33 @@ async function generateMigration(dir, opts = {}) {
19113
19151
  savePlanSnapshot(workspaceRoot, dialect, plan);
19114
19152
  return { sql, sqlStatements, hasChanges, plan, operations };
19115
19153
  }
19154
+ function supportsTransactionalDdl(dialect) {
19155
+ return dialect === "postgres" || dialect === "sqlite";
19156
+ }
19157
+ async function withMigrationLock(dialect, fn) {
19158
+ if (dialect === "sqlite")
19159
+ return await fn();
19160
+ const qb = createQueryBuilder();
19161
+ let reserved;
19162
+ try {
19163
+ reserved = await qb.reserve();
19164
+ } catch {
19165
+ return await fn();
19166
+ }
19167
+ let held = false;
19168
+ try {
19169
+ await reserved.advisoryLock(MIGRATION_LOCK_KEY);
19170
+ held = true;
19171
+ return await fn();
19172
+ } finally {
19173
+ try {
19174
+ if (held)
19175
+ await reserved.advisoryUnlock(MIGRATION_LOCK_KEY);
19176
+ } finally {
19177
+ reserved.release?.();
19178
+ }
19179
+ }
19180
+ }
19116
19181
  async function executeMigration(dir) {
19117
19182
  await getConfig();
19118
19183
  const workspaceRoot = getWorkspaceRoot();
@@ -19128,31 +19193,46 @@ async function executeMigration(dir) {
19128
19193
  try {
19129
19194
  const qb = createQueryBuilder();
19130
19195
  await createMigrationsTable(qb, dialect);
19131
- const executedMigrations = await getExecutedMigrations(qb, dialect);
19132
- const pending = scriptFiles.filter((file2) => !executedMigrations.includes(file2));
19133
- if (pending.length === 0) {
19134
- info("-- No pending migrations to execute");
19135
- return true;
19136
- }
19137
- info(`-- Executing ${pending.length} migration${pending.length === 1 ? "" : "s"}`);
19138
- for (const file2 of pending) {
19139
- const filePath = join10(sqlDir, file2);
19140
- info(`-- Executing: ${file2}`);
19141
- try {
19142
- await qb.file(filePath);
19143
- await recordMigration(qb, file2, dialect);
19144
- info(`-- \u2713 Migration ${file2} executed and recorded`);
19145
- } catch (err) {
19146
- if (isGeneratedMigration(sqlDir, file2) && isAlreadyAppliedError(err)) {
19147
- await recordMigration(qb, file2, dialect);
19148
- info(`-- \u2713 Migration ${file2} was already applied; recorded it (${err instanceof Error ? err.message : String(err)})`);
19149
- continue;
19196
+ return await withMigrationLock(dialect, async () => {
19197
+ const executedMigrations = await getExecutedMigrations(qb, dialect);
19198
+ const pending = scriptFiles.filter((file2) => !executedMigrations.includes(file2));
19199
+ if (pending.length === 0) {
19200
+ info("-- No pending migrations to execute");
19201
+ return true;
19202
+ }
19203
+ info(`-- Executing ${pending.length} migration${pending.length === 1 ? "" : "s"}`);
19204
+ for (const file2 of pending) {
19205
+ const filePath = join10(sqlDir, file2);
19206
+ info(`-- Executing: ${file2}`);
19207
+ try {
19208
+ const wrappable = supportsTransactionalDdl(dialect) && !NON_TRANSACTIONAL_SQL.test(readFileSync3(filePath, "utf-8"));
19209
+ if (wrappable) {
19210
+ await qb.transaction(async (tx) => {
19211
+ await tx.file(filePath);
19212
+ await recordMigration(tx, file2, dialect);
19213
+ });
19214
+ } else {
19215
+ await qb.file(filePath);
19216
+ await recordMigration(qb, file2, dialect);
19217
+ if (!supportsTransactionalDdl(dialect))
19218
+ info(`-- (${dialect} commits DDL implicitly, so ${file2} was not applied atomically)`);
19219
+ else
19220
+ info(`-- (${file2} contains a statement that cannot run in a transaction, so it was not applied atomically)`);
19221
+ }
19222
+ info(`-- \u2713 Migration ${file2} executed and recorded`);
19223
+ } catch (err) {
19224
+ if (isGeneratedMigration(sqlDir, file2) && isAlreadyAppliedError(err)) {
19225
+ await recordMigration(qb, file2, dialect);
19226
+ info(`-- \u2713 Migration ${file2} was already applied; recorded it (${err instanceof Error ? err.message : String(err)})`);
19227
+ continue;
19228
+ }
19229
+ console.error(`-- \u2717 Migration ${file2} failed:`, err);
19230
+ throw err;
19150
19231
  }
19151
- console.error(`-- \u2717 Migration ${file2} failed:`, err);
19152
- throw err;
19153
19232
  }
19154
- }
19155
- info("-- All migrations executed successfully");
19233
+ info("-- All migrations executed successfully");
19234
+ return true;
19235
+ });
19156
19236
  } catch (err) {
19157
19237
  console.error("-- Migration execution failed:", err);
19158
19238
  throw err;
@@ -19343,6 +19423,7 @@ async function recordMigration(qb, migrationFile, dialect) {
19343
19423
  throw err;
19344
19424
  }
19345
19425
  }
19426
+ var MIGRATION_LOCK_KEY = "bun-query-builder:migrate", NON_TRANSACTIONAL_SQL;
19346
19427
  var init_migrate = __esm(() => {
19347
19428
  init_config();
19348
19429
  init_db();
@@ -19350,6 +19431,7 @@ var init_migrate = __esm(() => {
19350
19431
  init_src2();
19351
19432
  init_introspect_db();
19352
19433
  init_workspace();
19434
+ NON_TRANSACTIONAL_SQL = /\bCONCURRENTLY\b|^\s*VACUUM\b/im;
19353
19435
  });
19354
19436
 
19355
19437
  // src/actions/migrate-generate.ts
@@ -25431,6 +25513,28 @@ function toTableName2(modelName) {
25431
25513
  tableNameCache.set(modelName, cached);
25432
25514
  return cached;
25433
25515
  }
25516
+ function relationNamesFor(definition, kind) {
25517
+ const rel = definition[kind];
25518
+ if (!rel)
25519
+ return [];
25520
+ if (Array.isArray(rel))
25521
+ return rel.map((item) => typeof item === "string" ? item : String(item?.model ?? "")).filter(Boolean);
25522
+ if (typeof rel === "object")
25523
+ return Object.keys(rel);
25524
+ if (typeof rel === "string")
25525
+ return [rel];
25526
+ return [];
25527
+ }
25528
+ function unresolvedRelationMessage(definition, relationName) {
25529
+ const model = definition.name ?? definition.table ?? "model";
25530
+ const declaredUnder = DECLARABLE_RELATION_KINDS.filter((kind) => relationNamesFor(definition, kind).some((n2) => n2.toLowerCase() === relationName.toLowerCase()));
25531
+ const unsupported = declaredUnder.filter((k2) => !RESOLVABLE_RELATION_KINDS.includes(k2));
25532
+ if (unsupported.length > 0) {
25533
+ return `[orm] '${model}' declares '${relationName}' as ${unsupported.join("/")}, which eager loading does not support yet. ` + `Supported kinds: ${RESOLVABLE_RELATION_KINDS.join(", ")}. ` + `Load it with a manual query for now \u2014 see stacksjs/bun-query-builder#1068.`;
25534
+ }
25535
+ const available = RESOLVABLE_RELATION_KINDS.flatMap((kind) => relationNamesFor(definition, kind));
25536
+ return available.length > 0 ? `[orm] '${model}' has no relation '${relationName}'. Available: ${available.join(", ")}.` : `[orm] '${model}' has no relation '${relationName}' \u2014 it declares no relations at all.`;
25537
+ }
25434
25538
  function resolveRelation(definition, relationName) {
25435
25539
  const parentName = definition.name;
25436
25540
  const parentTable = definition.table;
@@ -26165,7 +26269,7 @@ class ModelQueryBuilder {
26165
26269
  relationCache.set(cacheKey, rel);
26166
26270
  }
26167
26271
  if (!rel)
26168
- return;
26272
+ throw new Error(unresolvedRelationMessage(this._definition, relationName));
26169
26273
  if (rel.type === "hasMany" || rel.type === "hasOne") {
26170
26274
  const parentIds = distinctParentIds(instances, pk);
26171
26275
  if (parentIds.length === 0)
@@ -26757,7 +26861,7 @@ async function seedModel(definition, count, faker) {
26757
26861
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
26758
26862
  }
26759
26863
  }
26760
- var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
26864
+ var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000, RESOLVABLE_RELATION_KINDS, DECLARABLE_RELATION_KINDS;
26761
26865
  var init_orm = __esm(() => {
26762
26866
  init_config();
26763
26867
  init_db();
@@ -26768,6 +26872,17 @@ var init_orm = __esm(() => {
26768
26872
  snakeCaseCache = new Map;
26769
26873
  tableNameCache = new Map;
26770
26874
  relationCache = new Map;
26875
+ RESOLVABLE_RELATION_KINDS = ["hasMany", "hasOne", "belongsTo", "belongsToMany"];
26876
+ DECLARABLE_RELATION_KINDS = [
26877
+ ...RESOLVABLE_RELATION_KINDS,
26878
+ "hasOneThrough",
26879
+ "hasManyThrough",
26880
+ "morphOne",
26881
+ "morphMany",
26882
+ "morphTo",
26883
+ "morphToMany",
26884
+ "morphedByMany"
26885
+ ];
26771
26886
  });
26772
26887
 
26773
26888
  // src/model.ts
@@ -29751,6 +29866,7 @@ export {
29751
29866
  defineSeeder,
29752
29867
  defineModels,
29753
29868
  defineModel,
29869
+ defineConfig,
29754
29870
  defaultConfig4 as defaultConfig,
29755
29871
  dbWipe,
29756
29872
  dbStats,