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