@vibeorm/adapter-bun 2.0.0-alpha.8 → 2.0.0

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/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // packages/adapter-bun/src/index.ts
3
3
  var {SQL } = globalThis.Bun;
4
+ import { postgresDialect, renderDiagnostic } from "@vibeorm/sql";
4
5
 
5
6
  // packages/adapter-bun/src/connection-url.ts
6
7
  import { VibeError } from "@vibeorm/schema";
@@ -35,6 +36,7 @@ var BUN_SQLSTATE_ERROR_CODES = Object.freeze({
35
36
  "23502": "VIBE_VALIDATION",
36
37
  "23514": "VIBE_VALIDATION",
37
38
  "22P02": "VIBE_VALIDATION",
39
+ "42501": "VIBE_PERMISSION_DENIED",
38
40
  "40001": "VIBE_TRANSACTION",
39
41
  "40P01": "VIBE_TRANSACTION",
40
42
  "57014": "VIBE_TRANSACTION"
@@ -131,6 +133,128 @@ function serializeParamsRaw(params) {
131
133
  return params.values.map((value) => Array.isArray(value) ? toPgArrayLiteral(value) : value);
132
134
  }
133
135
 
136
+ // packages/adapter-bun/src/transaction-budget.ts
137
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
138
+ var DEFAULT_DEADLINE_CLOCK = () => Date.now();
139
+ function validateTransactionDeadline(params) {
140
+ const { options, support, provider } = params;
141
+ const deadline = options?.deadline;
142
+ if (deadline === undefined)
143
+ return;
144
+ if (support === "unsupported") {
145
+ throw new VibeError4({
146
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
147
+ message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
148
+ meta: { provider, feature: "transactionDeadline", supported: support }
149
+ });
150
+ }
151
+ if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
152
+ throw new VibeError4({
153
+ code: "VIBE_VALIDATION",
154
+ message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
155
+ meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
156
+ });
157
+ }
158
+ const requested = deadline.enforcement ?? "between-statements";
159
+ if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
160
+ throw new VibeError4({
161
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
162
+ message: `${provider} cannot cancel a statement that is already running, so the requested ` + "`cancel-running-statements` deadline guarantee is refused rather than silently downgraded \u2014 " + 'ask for "between-statements" if a budget checked between statements is enough',
163
+ meta: { provider, feature: "transactionDeadline", requested, supported: support }
164
+ });
165
+ }
166
+ }
167
+ function startTransactionBudget(params) {
168
+ const { options, provider } = params;
169
+ const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;
170
+ const deadline = options?.deadline;
171
+ const totalMs = deadline === undefined ? null : deadline.totalMs;
172
+ const enforcement = deadline === undefined ? null : deadline.enforcement ?? "between-statements";
173
+ const expiresAt = totalMs === null ? null : clock() + totalMs;
174
+ let outcome = null;
175
+ const budget = {
176
+ enforcement,
177
+ totalMs,
178
+ statementTimeoutMs: options?.timeout ?? null,
179
+ remainingMs() {
180
+ return expiresAt === null ? null : Math.max(0, expiresAt - clock());
181
+ },
182
+ expired() {
183
+ return expiresAt !== null && clock() >= expiresAt;
184
+ },
185
+ assertUsable(assertParams) {
186
+ if (outcome !== null) {
187
+ throw transactionClosedError({ provider, outcome, stage: assertParams.stage });
188
+ }
189
+ if (expiresAt !== null && clock() >= expiresAt) {
190
+ throw transactionDeadlineError({
191
+ provider,
192
+ stage: assertParams.stage,
193
+ totalMs,
194
+ overdueMs: clock() - expiresAt
195
+ });
196
+ }
197
+ },
198
+ close(closeParams) {
199
+ if (outcome === null)
200
+ outcome = closeParams.outcome;
201
+ },
202
+ get outcome() {
203
+ return outcome;
204
+ }
205
+ };
206
+ return budget;
207
+ }
208
+ function engineStatementBudgetMs(params) {
209
+ const { budget } = params;
210
+ if (budget.enforcement !== "cancel-running-statements")
211
+ return null;
212
+ const remaining = budget.remainingMs();
213
+ if (remaining === null)
214
+ return null;
215
+ const totalBound = Math.max(1, Math.ceil(remaining));
216
+ return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0 ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;
217
+ }
218
+ function transactionDeadlineError(params) {
219
+ const { provider, stage, totalMs, overdueMs } = params;
220
+ const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
221
+ return new VibeError4({
222
+ code: "VIBE_TRANSACTION",
223
+ message: `transaction deadline of ${totalMs} ms expired ${Math.max(0, Math.round(overdueMs))} ms ago \u2014 ${what}. ` + "Nothing is retried automatically: work the callback did outside the database may already have happened.",
224
+ meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
225
+ });
226
+ }
227
+ function transactionClosedError(params) {
228
+ const { provider, outcome, stage } = params;
229
+ return new VibeError4({
230
+ code: "VIBE_TRANSACTION",
231
+ message: `this transaction has already finished (${outcome}), so its client can no longer be used \u2014 ` + "a handle kept past the end of `$transaction` would run outside the transaction, on a connection " + "that now belongs to someone else",
232
+ meta: { provider, reason: "transaction-closed", outcome, stage }
233
+ });
234
+ }
235
+ function transactionOutcomeUnknownError(params) {
236
+ const { provider, totalMs, cause } = params;
237
+ return new VibeError4({
238
+ code: "VIBE_TRANSACTION",
239
+ message: `transaction deadline of ${totalMs} ms expired and the rollback that should have cleaned up failed \u2014 ` + "the commit state of this transaction is UNKNOWN. It is not retried automatically; inspect the data " + "before deciding.",
240
+ meta: {
241
+ provider,
242
+ reason: "transaction-outcome-unknown",
243
+ outcome: "unknown",
244
+ totalMs,
245
+ rollbackFailed: true
246
+ },
247
+ cause
248
+ });
249
+ }
250
+ function isTransactionDeadlineError(error) {
251
+ const reason = error?.meta?.reason;
252
+ return reason === "transaction-deadline";
253
+ }
254
+
255
+ // packages/adapter-bun/src/savepoint-gate.ts
256
+ import { AsyncLocalStorage } from "async_hooks";
257
+
134
258
  // packages/adapter-bun/src/savepoints.ts
135
259
  function createSavepointCounter() {
136
260
  return { n: 0 };
@@ -139,9 +263,224 @@ function nextSavepointName(params) {
139
263
  return `vibeorm_sp_${params.counter.n++}`;
140
264
  }
141
265
 
266
+ // packages/adapter-bun/src/savepoint-gate.ts
267
+ var CURRENT = new AsyncLocalStorage;
268
+ function createSavepointScope() {
269
+ return { depth: 0, state: { counter: createSavepointCounter(), open: null } };
270
+ }
271
+ function runRootScope(params) {
272
+ return CURRENT.run(params.scope, params.body);
273
+ }
274
+ function scopeTurn(params) {
275
+ const open = params.scope.state.open;
276
+ if (mayProceed({ scope: params.scope, open }))
277
+ return;
278
+ return waitForTurn({ scope: params.scope });
279
+ }
280
+ async function runNestedSavepoint(params) {
281
+ const { scope, execute, body } = params;
282
+ const node = await acquire({ scope });
283
+ try {
284
+ if (execute !== undefined)
285
+ await execute(`SAVEPOINT ${node.name}`);
286
+ let result;
287
+ try {
288
+ result = await CURRENT.run(node.scope, () => body(node.scope));
289
+ } catch (error) {
290
+ await drainDescendants({ node });
291
+ if (execute !== undefined) {
292
+ try {
293
+ await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);
294
+ } catch {}
295
+ }
296
+ throw error;
297
+ }
298
+ await drainDescendants({ node });
299
+ if (execute !== undefined)
300
+ await execute(`RELEASE SAVEPOINT ${node.name}`);
301
+ return result;
302
+ } finally {
303
+ closeNode({ node });
304
+ }
305
+ }
306
+ function mayProceed(params) {
307
+ const { scope, open } = params;
308
+ if (open === null)
309
+ return true;
310
+ if (open.scope === scope)
311
+ return true;
312
+ if (open.scope.depth < scope.depth)
313
+ return true;
314
+ return CURRENT.getStore() === open.scope;
315
+ }
316
+ async function waitForTurn(params) {
317
+ for (;; ) {
318
+ const open = params.scope.state.open;
319
+ if (open === null || mayProceed({ scope: params.scope, open }))
320
+ return;
321
+ await open.settled;
322
+ }
323
+ }
324
+ async function acquire(params) {
325
+ const { scope } = params;
326
+ for (;; ) {
327
+ const open = scope.state.open;
328
+ if (mayProceed({ scope, open }))
329
+ return pushNode({ scope, parent: open });
330
+ await open.settled;
331
+ }
332
+ }
333
+ function pushNode(params) {
334
+ const { scope, parent } = params;
335
+ const state = scope.state;
336
+ let settle = () => {
337
+ return;
338
+ };
339
+ const settled = new Promise((resolve) => {
340
+ settle = () => resolve();
341
+ });
342
+ const node = {
343
+ scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },
344
+ name: nextSavepointName({ counter: state.counter }),
345
+ parent,
346
+ settled,
347
+ settle
348
+ };
349
+ state.open = node;
350
+ return node;
351
+ }
352
+ function closeNode(params) {
353
+ const { node } = params;
354
+ const state = node.scope.state;
355
+ if (state.open === node)
356
+ state.open = node.parent;
357
+ node.settle();
358
+ }
359
+ async function drainDescendants(params) {
360
+ const state = params.node.scope.state;
361
+ for (;; ) {
362
+ const open = state.open;
363
+ if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth)
364
+ return;
365
+ await open.settled;
366
+ }
367
+ }
368
+
142
369
  // packages/adapter-bun/src/template-cache.ts
370
+ import { VibeError as VibeError5 } from "@vibeorm/schema";
371
+ function isIdentChar(char) {
372
+ if (char === undefined)
373
+ return false;
374
+ return /[A-Za-z0-9_]/.test(char);
375
+ }
376
+ function skipQuoted(text, start, quote) {
377
+ const escaping = quote === "'" && (text[start - 1] === "e" || text[start - 1] === "E") && !isIdentChar(text[start - 2]);
378
+ let index = start + 1;
379
+ while (index < text.length) {
380
+ const char = text[index];
381
+ if (escaping && char === "\\") {
382
+ index += 2;
383
+ continue;
384
+ }
385
+ if (char === quote) {
386
+ if (text[index + 1] === quote) {
387
+ index += 2;
388
+ continue;
389
+ }
390
+ return index + 1;
391
+ }
392
+ index += 1;
393
+ }
394
+ return text.length;
395
+ }
396
+ function skipBlockComment(text, start) {
397
+ let depth = 0;
398
+ let index = start;
399
+ while (index < text.length) {
400
+ if (text[index] === "/" && text[index + 1] === "*") {
401
+ depth += 1;
402
+ index += 2;
403
+ continue;
404
+ }
405
+ if (text[index] === "*" && text[index + 1] === "/") {
406
+ depth -= 1;
407
+ index += 2;
408
+ if (depth === 0)
409
+ return index;
410
+ continue;
411
+ }
412
+ index += 1;
413
+ }
414
+ return text.length;
415
+ }
416
+ function skipDollarQuoted(text, start) {
417
+ const opener = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(start));
418
+ if (opener === null)
419
+ return -1;
420
+ const tag = opener[0];
421
+ const close = text.indexOf(tag, start + tag.length);
422
+ return close === -1 ? text.length : close + tag.length;
423
+ }
424
+ function scanPlaceholders(params) {
425
+ const { text } = params;
426
+ const chunks = [];
427
+ const indices = [];
428
+ let chunkStart = 0;
429
+ let index = 0;
430
+ while (index < text.length) {
431
+ const char = text[index];
432
+ if (char === "'" || char === '"') {
433
+ index = skipQuoted(text, index, char);
434
+ continue;
435
+ }
436
+ if (char === "-" && text[index + 1] === "-") {
437
+ const newline = text.indexOf(`
438
+ `, index);
439
+ index = newline === -1 ? text.length : newline + 1;
440
+ continue;
441
+ }
442
+ if (char === "/" && text[index + 1] === "*") {
443
+ index = skipBlockComment(text, index);
444
+ continue;
445
+ }
446
+ if (char === "$") {
447
+ const dollarEnd = skipDollarQuoted(text, index);
448
+ if (dollarEnd !== -1) {
449
+ index = dollarEnd;
450
+ continue;
451
+ }
452
+ let digits = index + 1;
453
+ while (digits < text.length && text[digits] !== undefined && /[0-9]/.test(text[digits]))
454
+ digits += 1;
455
+ if (digits > index + 1) {
456
+ chunks.push(text.slice(chunkStart, index));
457
+ indices.push(Number(text.slice(index + 1, digits)));
458
+ chunkStart = digits;
459
+ index = digits;
460
+ continue;
461
+ }
462
+ }
463
+ index += 1;
464
+ }
465
+ chunks.push(text.slice(chunkStart));
466
+ const template = Object.assign(chunks, { raw: chunks });
467
+ return { template, indices };
468
+ }
143
469
  function templateChunks(params) {
144
- return params.text.split(/\$\d+/);
470
+ return [...scanPlaceholders(params).template];
471
+ }
472
+ function bindTemplateValues(params) {
473
+ const { text, indices, values } = params;
474
+ return indices.map((placeholder) => {
475
+ if (placeholder < 1 || placeholder > values.length) {
476
+ throw new VibeError5({
477
+ code: "VIBE_VALIDATION",
478
+ message: `bun:sql prepared statement references $${placeholder} but only ${values.length} parameter(s) were given`,
479
+ meta: { text, placeholder, provided: values.length }
480
+ });
481
+ }
482
+ return values[placeholder - 1];
483
+ });
145
484
  }
146
485
  function createTemplateCache(params) {
147
486
  const { max } = params;
@@ -155,15 +494,14 @@ function createTemplateCache(params) {
155
494
  templates.set(text, existing);
156
495
  return existing;
157
496
  }
158
- const chunks = templateChunks({ text });
159
- const template = Object.assign(chunks, { raw: chunks });
497
+ const prepared = scanPlaceholders({ text });
160
498
  if (templates.size >= max) {
161
499
  const oldest = templates.keys().next().value;
162
500
  if (oldest !== undefined)
163
501
  templates.delete(oldest);
164
502
  }
165
- templates.set(text, template);
166
- return template;
503
+ templates.set(text, prepared);
504
+ return prepared;
167
505
  },
168
506
  size() {
169
507
  return templates.size;
@@ -172,7 +510,7 @@ function createTemplateCache(params) {
172
510
  }
173
511
 
174
512
  // packages/adapter-bun/src/transaction-sql.ts
175
- import { VibeError as VibeError4 } from "@vibeorm/schema";
513
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
176
514
  var ISOLATION_LEVEL_SQL = Object.freeze({
177
515
  ReadCommitted: "READ COMMITTED",
178
516
  RepeatableRead: "REPEATABLE READ",
@@ -185,7 +523,7 @@ function beginStatement(params) {
185
523
  function statementTimeoutStatement(params) {
186
524
  const { timeout } = params;
187
525
  if (!Number.isInteger(timeout) || timeout < 0) {
188
- throw new VibeError4({
526
+ throw new VibeError6({
189
527
  code: "VIBE_VALIDATION",
190
528
  message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
191
529
  meta: { timeout }
@@ -211,6 +549,8 @@ function refuseNestedTransactionOptions(params) {
211
549
  passed.push("isolationLevel");
212
550
  if (options.timeout !== undefined)
213
551
  passed.push("timeout");
552
+ if (options.deadline !== undefined)
553
+ passed.push("deadline");
214
554
  if (passed.length === 0)
215
555
  return;
216
556
  const meta = { provider, nested: true };
@@ -218,9 +558,11 @@ function refuseNestedTransactionOptions(params) {
218
558
  meta.isolationLevel = options.isolationLevel;
219
559
  if (options.timeout !== undefined)
220
560
  meta.timeout = options.timeout;
221
- throw new VibeError4({
561
+ if (options.deadline !== undefined)
562
+ meta.deadline = options.deadline;
563
+ throw new VibeError6({
222
564
  code: "VIBE_VALIDATION",
223
- message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins, and a nested timeout is not enforceable; pass options on the top-level $transaction`,
565
+ message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins, a nested timeout is not enforceable, and a nested deadline would need its own session setting on a connection it does not own (it inherits the top-level one instead); pass options on the top-level $transaction`,
224
566
  meta
225
567
  });
226
568
  }
@@ -236,10 +578,19 @@ class CallbackError extends Error {
236
578
  }
237
579
  var DIALECT = "postgres";
238
580
  var PROVIDER = "postgresql";
239
- var WIRE = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
581
+ var BUDGETS = Object.freeze({
582
+ acquireTimeout: true,
583
+ statementTimeout: "all-statements",
584
+ transactionDeadline: "cancel-running-statements"
585
+ });
586
+ var WIRE_TEXT = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
587
+ var WIRE_PARSED = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
240
588
  function bunAdapter(options) {
241
589
  const usePrepared = options?.preparedStatements ?? false;
242
590
  const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });
591
+ let activeWire = WIRE_TEXT;
592
+ let wireSettled = false;
593
+ let wireProbe = null;
243
594
  let instance = null;
244
595
  let rawSession = null;
245
596
  function getSql() {
@@ -258,11 +609,27 @@ function bunAdapter(options) {
258
609
  instance = url === undefined ? new SQL(sqlOptions) : new SQL(url, sqlOptions);
259
610
  return instance;
260
611
  }
612
+ async function probeJsonWire(source) {
613
+ const probe = renderDiagnostic({ dialect: postgresDialect, query: "postgresJsonWire" });
614
+ try {
615
+ const rows = usePrepared ? await source(templates.getTemplate({ text: probe.text }).template) : await source.unsafe(probe.text, []);
616
+ const probed = rows[0]?.probe;
617
+ if (typeof probed === "object" && probed !== null)
618
+ activeWire = WIRE_PARSED;
619
+ } catch {} finally {
620
+ wireSettled = true;
621
+ }
622
+ }
261
623
  async function runExecute(params) {
624
+ if (!wireSettled) {
625
+ wireProbe ??= probeJsonWire(params.source);
626
+ await wireProbe;
627
+ }
262
628
  const values = serializeParams({ values: params.values });
263
629
  if (usePrepared) {
264
- const template = templates.getTemplate({ text: params.text });
265
- return runMapped(() => params.source(template, ...values));
630
+ const prepared = templates.getTemplate({ text: params.text });
631
+ const bound = bindTemplateValues({ text: params.text, indices: prepared.indices, values });
632
+ return runMapped(() => params.source(prepared.template, ...bound));
266
633
  }
267
634
  return runMapped(() => params.source.unsafe(params.text, values));
268
635
  }
@@ -272,61 +639,79 @@ function bunAdapter(options) {
272
639
  return { rows, affectedRows: readAffectedRows({ rows }) };
273
640
  }
274
641
  function createAdapter(params) {
275
- const { source, parentMode, savepointCounter } = params;
642
+ const { source, parentMode, scope, budget } = params;
643
+ async function applyEngineBudget() {
644
+ if (budget === undefined)
645
+ return;
646
+ const ms = engineStatementBudgetMs({ budget });
647
+ if (ms === null)
648
+ return;
649
+ await runMapped(() => source.unsafe(statementTimeoutStatement({ timeout: ms })));
650
+ }
276
651
  async function transactionViaSavepoint(fn, nestedOptions) {
277
652
  refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
653
+ budget?.assertUsable({ stage: "nested" });
278
654
  const transactional = source;
279
- if (typeof transactional.savepoint === "function") {
280
- try {
281
- return await transactional.savepoint(async (savepointSql) => {
282
- const nested = createAdapter({
283
- source: savepointSql,
284
- parentMode: "tx",
285
- savepointCounter
286
- });
655
+ const driverSavepoint = transactional.savepoint?.bind(transactional);
656
+ if (driverSavepoint !== undefined) {
657
+ return runNestedSavepoint({
658
+ scope,
659
+ body: async (child) => {
287
660
  try {
288
- return await fn(nested);
661
+ return await driverSavepoint(async (savepointSql) => {
662
+ const nested = createAdapter({
663
+ source: savepointSql,
664
+ parentMode: "tx",
665
+ scope: child
666
+ });
667
+ try {
668
+ return await fn(nested);
669
+ } catch (error) {
670
+ throw new CallbackError(error);
671
+ }
672
+ });
289
673
  } catch (error) {
290
- throw new CallbackError(error);
674
+ if (error instanceof CallbackError)
675
+ throw error.inner;
676
+ throw mapBunDriverError({ error });
291
677
  }
292
- });
293
- } catch (error) {
294
- if (error instanceof CallbackError)
295
- throw error.inner;
296
- throw mapBunDriverError({ error });
297
- }
298
- }
299
- const savepoint = nextSavepointName({ counter: savepointCounter });
300
- await runMapped(() => transactional.unsafe(`SAVEPOINT ${savepoint}`));
301
- try {
302
- const nested = createAdapter({ source: transactional, parentMode: "tx", savepointCounter });
303
- const result = await fn(nested);
304
- await runMapped(() => transactional.unsafe(`RELEASE SAVEPOINT ${savepoint}`));
305
- return result;
306
- } catch (error) {
307
- try {
308
- await transactional.unsafe(`ROLLBACK TO SAVEPOINT ${savepoint}`);
309
- } catch {}
310
- throw error;
678
+ }
679
+ });
311
680
  }
681
+ return runNestedSavepoint({
682
+ scope,
683
+ execute: (statement) => runMapped(() => transactional.unsafe(statement)),
684
+ body: (child) => fn(createAdapter({ source: transactional, parentMode: "tx", scope: child, budget }))
685
+ });
312
686
  }
313
687
  async function transactionViaPool(fn, transactionOptions) {
314
688
  const pool = source;
315
- if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined) {
689
+ validateTransactionDeadline({
690
+ options: transactionOptions,
691
+ support: BUDGETS.transactionDeadline,
692
+ provider: PROVIDER
693
+ });
694
+ if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined && transactionOptions?.deadline === undefined) {
695
+ const budget3 = startTransactionBudget({ provider: PROVIDER });
316
696
  try {
317
- return await pool.begin(async (txSql) => {
697
+ const result = await pool.begin(async (txSql) => {
698
+ const txScope = createSavepointScope();
318
699
  const txAdapter = createAdapter({
319
700
  source: txSql,
320
701
  parentMode: "tx",
321
- savepointCounter: createSavepointCounter()
702
+ scope: txScope,
703
+ budget: budget3
322
704
  });
323
705
  try {
324
- return await fn(txAdapter);
706
+ return await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
325
707
  } catch (error) {
326
708
  throw new CallbackError(error);
327
709
  }
328
710
  });
711
+ budget3.close({ outcome: "committed" });
712
+ return result;
329
713
  } catch (error) {
714
+ budget3.close({ outcome: "rolled-back" });
330
715
  if (error instanceof CallbackError)
331
716
  throw error.inner;
332
717
  throw mapBunDriverError({ error });
@@ -334,22 +719,37 @@ function bunAdapter(options) {
334
719
  }
335
720
  const timeoutText = transactionOptions.timeout === undefined ? undefined : statementTimeoutStatement({ timeout: transactionOptions.timeout });
336
721
  const reserved = await runMapped(() => pool.reserve());
722
+ const budget2 = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
337
723
  try {
338
724
  await runMapped(() => reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })));
339
725
  if (timeoutText !== undefined)
340
726
  await runMapped(() => reserved.unsafe(timeoutText));
727
+ const txScope = createSavepointScope();
341
728
  const txAdapter = createAdapter({
342
729
  source: reserved,
343
730
  parentMode: "tx",
344
- savepointCounter: createSavepointCounter()
731
+ scope: txScope,
732
+ budget: budget2
345
733
  });
346
- const result = await fn(txAdapter);
734
+ const result = await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
735
+ budget2.assertUsable({ stage: "commit" });
347
736
  await runMapped(() => reserved.unsafe("COMMIT"));
737
+ budget2.close({ outcome: "committed" });
348
738
  return result;
349
739
  } catch (error) {
350
740
  try {
351
741
  await reserved.unsafe("ROLLBACK");
352
- } catch {}
742
+ budget2.close({ outcome: "rolled-back" });
743
+ } catch {
744
+ budget2.close({ outcome: "unknown" });
745
+ if (budget2.totalMs !== null && isTransactionDeadlineError(error)) {
746
+ throw transactionOutcomeUnknownError({
747
+ provider: PROVIDER,
748
+ totalMs: budget2.totalMs,
749
+ cause: error
750
+ });
751
+ }
752
+ }
353
753
  throw error;
354
754
  } finally {
355
755
  reserved.release();
@@ -358,11 +758,24 @@ function bunAdapter(options) {
358
758
  return {
359
759
  dialect: DIALECT,
360
760
  provider: PROVIDER,
361
- wire: WIRE,
761
+ budgets: BUDGETS,
762
+ get wire() {
763
+ return activeWire;
764
+ },
362
765
  async execute(execParams) {
766
+ const turn = scopeTurn({ scope });
767
+ if (turn !== undefined)
768
+ await turn;
769
+ budget?.assertUsable({ stage: "statement" });
770
+ await applyEngineBudget();
363
771
  return runExecute({ source, ...execParams });
364
772
  },
365
773
  async executeUnsafe(execParams) {
774
+ const turn = scopeTurn({ scope });
775
+ if (turn !== undefined)
776
+ await turn;
777
+ budget?.assertUsable({ stage: "statement" });
778
+ await applyEngineBudget();
366
779
  return runExecuteUnsafe({ source, ...execParams });
367
780
  },
368
781
  async transaction(fn, transactionOptions) {
@@ -389,13 +802,16 @@ function bunAdapter(options) {
389
802
  return createAdapter({
390
803
  source: getSql(),
391
804
  parentMode: "pool",
392
- savepointCounter: createSavepointCounter()
805
+ scope: createSavepointScope()
393
806
  });
394
807
  }
395
808
  return {
396
809
  dialect: DIALECT,
397
810
  provider: PROVIDER,
398
- wire: WIRE,
811
+ budgets: BUDGETS,
812
+ get wire() {
813
+ return activeWire;
814
+ },
399
815
  async execute(params) {
400
816
  return poolAdapter().execute(params);
401
817
  },
@@ -461,14 +877,21 @@ function readAffectedRows(params) {
461
877
  return rows.length;
462
878
  }
463
879
  export {
880
+ validateTransactionDeadline,
881
+ transactionOutcomeUnknownError,
882
+ transactionDeadlineError,
883
+ transactionClosedError,
464
884
  toPgArrayLiteral,
465
885
  templateChunks,
466
886
  statementTimeoutStatement,
887
+ startTransactionBudget,
467
888
  serializeParamsRaw,
468
889
  serializeParams,
469
890
  refuseNestedTransactionOptions,
470
891
  nextSavepointName,
471
892
  mapBunDriverError,
893
+ isTransactionDeadlineError,
894
+ engineStatementBudgetMs,
472
895
  createTemplateCache,
473
896
  createSavepointCounter,
474
897
  classifyRawTransactionControl,
@@ -478,8 +901,9 @@ export {
478
901
  beginStatement,
479
902
  applyStartupOptions,
480
903
  ISOLATION_LEVEL_SQL,
904
+ DEFAULT_DEADLINE_CLOCK,
481
905
  BUN_SQLSTATE_REASONS,
482
906
  BUN_SQLSTATE_ERROR_CODES
483
907
  };
484
908
 
485
- //# debugId=08E3C28E82D7C93A64756E2164756E21
909
+ //# debugId=81824F43B962EC8264756E2164756E21