@vibeorm/adapter-bun 2.0.0-alpha.9 → 2.0.1

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"
@@ -124,13 +126,150 @@ function toPgArrayLiteral(values) {
124
126
  });
125
127
  return `{${elements.join(",")}}`;
126
128
  }
129
+ function serializeDate(params) {
130
+ const { value } = params;
131
+ if (Number.isNaN(value.getTime()))
132
+ return value;
133
+ const year = value.getUTCFullYear();
134
+ if (year < 1 || year > 9999)
135
+ return value;
136
+ return value.toISOString();
137
+ }
127
138
  function serializeParams(params) {
128
- return params.values.slice();
139
+ return params.values.map((value) => value instanceof Date ? serializeDate({ value }) : value);
129
140
  }
130
141
  function serializeParamsRaw(params) {
131
- return params.values.map((value) => Array.isArray(value) ? toPgArrayLiteral(value) : value);
142
+ return params.values.map((value) => {
143
+ if (Array.isArray(value))
144
+ return toPgArrayLiteral(value);
145
+ if (value instanceof Date)
146
+ return serializeDate({ value });
147
+ return value;
148
+ });
149
+ }
150
+
151
+ // packages/adapter-bun/src/transaction-budget.ts
152
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
153
+ var DEFAULT_DEADLINE_CLOCK = () => Date.now();
154
+ function validateTransactionDeadline(params) {
155
+ const { options, support, provider } = params;
156
+ const deadline = options?.deadline;
157
+ if (deadline === undefined)
158
+ return;
159
+ if (support === "unsupported") {
160
+ throw new VibeError4({
161
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
162
+ message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
163
+ meta: { provider, feature: "transactionDeadline", supported: support }
164
+ });
165
+ }
166
+ if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
167
+ throw new VibeError4({
168
+ code: "VIBE_VALIDATION",
169
+ message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
170
+ meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
171
+ });
172
+ }
173
+ const requested = deadline.enforcement ?? "between-statements";
174
+ if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
175
+ throw new VibeError4({
176
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
177
+ 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',
178
+ meta: { provider, feature: "transactionDeadline", requested, supported: support }
179
+ });
180
+ }
181
+ }
182
+ function startTransactionBudget(params) {
183
+ const { options, provider } = params;
184
+ const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;
185
+ const deadline = options?.deadline;
186
+ const totalMs = deadline === undefined ? null : deadline.totalMs;
187
+ const enforcement = deadline === undefined ? null : deadline.enforcement ?? "between-statements";
188
+ const expiresAt = totalMs === null ? null : clock() + totalMs;
189
+ let outcome = null;
190
+ const budget = {
191
+ enforcement,
192
+ totalMs,
193
+ statementTimeoutMs: options?.timeout ?? null,
194
+ remainingMs() {
195
+ return expiresAt === null ? null : Math.max(0, expiresAt - clock());
196
+ },
197
+ expired() {
198
+ return expiresAt !== null && clock() >= expiresAt;
199
+ },
200
+ assertUsable(assertParams) {
201
+ if (outcome !== null) {
202
+ throw transactionClosedError({ provider, outcome, stage: assertParams.stage });
203
+ }
204
+ if (expiresAt !== null && clock() >= expiresAt) {
205
+ throw transactionDeadlineError({
206
+ provider,
207
+ stage: assertParams.stage,
208
+ totalMs,
209
+ overdueMs: clock() - expiresAt
210
+ });
211
+ }
212
+ },
213
+ close(closeParams) {
214
+ if (outcome === null)
215
+ outcome = closeParams.outcome;
216
+ },
217
+ get outcome() {
218
+ return outcome;
219
+ }
220
+ };
221
+ return budget;
222
+ }
223
+ function engineStatementBudgetMs(params) {
224
+ const { budget } = params;
225
+ if (budget.enforcement !== "cancel-running-statements")
226
+ return null;
227
+ const remaining = budget.remainingMs();
228
+ if (remaining === null)
229
+ return null;
230
+ const totalBound = Math.max(1, Math.ceil(remaining));
231
+ return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0 ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;
232
+ }
233
+ function transactionDeadlineError(params) {
234
+ const { provider, stage, totalMs, overdueMs } = params;
235
+ const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
236
+ return new VibeError4({
237
+ code: "VIBE_TRANSACTION",
238
+ 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.",
239
+ meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
240
+ });
241
+ }
242
+ function transactionClosedError(params) {
243
+ const { provider, outcome, stage } = params;
244
+ return new VibeError4({
245
+ code: "VIBE_TRANSACTION",
246
+ 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",
247
+ meta: { provider, reason: "transaction-closed", outcome, stage }
248
+ });
249
+ }
250
+ function transactionOutcomeUnknownError(params) {
251
+ const { provider, totalMs, cause } = params;
252
+ return new VibeError4({
253
+ code: "VIBE_TRANSACTION",
254
+ 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.",
255
+ meta: {
256
+ provider,
257
+ reason: "transaction-outcome-unknown",
258
+ outcome: "unknown",
259
+ totalMs,
260
+ rollbackFailed: true
261
+ },
262
+ cause
263
+ });
264
+ }
265
+ function isTransactionDeadlineError(error) {
266
+ const reason = error?.meta?.reason;
267
+ return reason === "transaction-deadline";
132
268
  }
133
269
 
270
+ // packages/adapter-bun/src/savepoint-gate.ts
271
+ import { AsyncLocalStorage } from "async_hooks";
272
+
134
273
  // packages/adapter-bun/src/savepoints.ts
135
274
  function createSavepointCounter() {
136
275
  return { n: 0 };
@@ -139,9 +278,224 @@ function nextSavepointName(params) {
139
278
  return `vibeorm_sp_${params.counter.n++}`;
140
279
  }
141
280
 
281
+ // packages/adapter-bun/src/savepoint-gate.ts
282
+ var CURRENT = new AsyncLocalStorage;
283
+ function createSavepointScope() {
284
+ return { depth: 0, state: { counter: createSavepointCounter(), open: null } };
285
+ }
286
+ function runRootScope(params) {
287
+ return CURRENT.run(params.scope, params.body);
288
+ }
289
+ function scopeTurn(params) {
290
+ const open = params.scope.state.open;
291
+ if (mayProceed({ scope: params.scope, open }))
292
+ return;
293
+ return waitForTurn({ scope: params.scope });
294
+ }
295
+ async function runNestedSavepoint(params) {
296
+ const { scope, execute, body } = params;
297
+ const node = await acquire({ scope });
298
+ try {
299
+ if (execute !== undefined)
300
+ await execute(`SAVEPOINT ${node.name}`);
301
+ let result;
302
+ try {
303
+ result = await CURRENT.run(node.scope, () => body(node.scope));
304
+ } catch (error) {
305
+ await drainDescendants({ node });
306
+ if (execute !== undefined) {
307
+ try {
308
+ await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);
309
+ } catch {}
310
+ }
311
+ throw error;
312
+ }
313
+ await drainDescendants({ node });
314
+ if (execute !== undefined)
315
+ await execute(`RELEASE SAVEPOINT ${node.name}`);
316
+ return result;
317
+ } finally {
318
+ closeNode({ node });
319
+ }
320
+ }
321
+ function mayProceed(params) {
322
+ const { scope, open } = params;
323
+ if (open === null)
324
+ return true;
325
+ if (open.scope === scope)
326
+ return true;
327
+ if (open.scope.depth < scope.depth)
328
+ return true;
329
+ return CURRENT.getStore() === open.scope;
330
+ }
331
+ async function waitForTurn(params) {
332
+ for (;; ) {
333
+ const open = params.scope.state.open;
334
+ if (open === null || mayProceed({ scope: params.scope, open }))
335
+ return;
336
+ await open.settled;
337
+ }
338
+ }
339
+ async function acquire(params) {
340
+ const { scope } = params;
341
+ for (;; ) {
342
+ const open = scope.state.open;
343
+ if (mayProceed({ scope, open }))
344
+ return pushNode({ scope, parent: open });
345
+ await open.settled;
346
+ }
347
+ }
348
+ function pushNode(params) {
349
+ const { scope, parent } = params;
350
+ const state = scope.state;
351
+ let settle = () => {
352
+ return;
353
+ };
354
+ const settled = new Promise((resolve) => {
355
+ settle = () => resolve();
356
+ });
357
+ const node = {
358
+ scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },
359
+ name: nextSavepointName({ counter: state.counter }),
360
+ parent,
361
+ settled,
362
+ settle
363
+ };
364
+ state.open = node;
365
+ return node;
366
+ }
367
+ function closeNode(params) {
368
+ const { node } = params;
369
+ const state = node.scope.state;
370
+ if (state.open === node)
371
+ state.open = node.parent;
372
+ node.settle();
373
+ }
374
+ async function drainDescendants(params) {
375
+ const state = params.node.scope.state;
376
+ for (;; ) {
377
+ const open = state.open;
378
+ if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth)
379
+ return;
380
+ await open.settled;
381
+ }
382
+ }
383
+
142
384
  // packages/adapter-bun/src/template-cache.ts
385
+ import { VibeError as VibeError5 } from "@vibeorm/schema";
386
+ function isIdentChar(char) {
387
+ if (char === undefined)
388
+ return false;
389
+ return /[A-Za-z0-9_]/.test(char);
390
+ }
391
+ function skipQuoted(text, start, quote) {
392
+ const escaping = quote === "'" && (text[start - 1] === "e" || text[start - 1] === "E") && !isIdentChar(text[start - 2]);
393
+ let index = start + 1;
394
+ while (index < text.length) {
395
+ const char = text[index];
396
+ if (escaping && char === "\\") {
397
+ index += 2;
398
+ continue;
399
+ }
400
+ if (char === quote) {
401
+ if (text[index + 1] === quote) {
402
+ index += 2;
403
+ continue;
404
+ }
405
+ return index + 1;
406
+ }
407
+ index += 1;
408
+ }
409
+ return text.length;
410
+ }
411
+ function skipBlockComment(text, start) {
412
+ let depth = 0;
413
+ let index = start;
414
+ while (index < text.length) {
415
+ if (text[index] === "/" && text[index + 1] === "*") {
416
+ depth += 1;
417
+ index += 2;
418
+ continue;
419
+ }
420
+ if (text[index] === "*" && text[index + 1] === "/") {
421
+ depth -= 1;
422
+ index += 2;
423
+ if (depth === 0)
424
+ return index;
425
+ continue;
426
+ }
427
+ index += 1;
428
+ }
429
+ return text.length;
430
+ }
431
+ function skipDollarQuoted(text, start) {
432
+ const opener = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(start));
433
+ if (opener === null)
434
+ return -1;
435
+ const tag = opener[0];
436
+ const close = text.indexOf(tag, start + tag.length);
437
+ return close === -1 ? text.length : close + tag.length;
438
+ }
439
+ function scanPlaceholders(params) {
440
+ const { text } = params;
441
+ const chunks = [];
442
+ const indices = [];
443
+ let chunkStart = 0;
444
+ let index = 0;
445
+ while (index < text.length) {
446
+ const char = text[index];
447
+ if (char === "'" || char === '"') {
448
+ index = skipQuoted(text, index, char);
449
+ continue;
450
+ }
451
+ if (char === "-" && text[index + 1] === "-") {
452
+ const newline = text.indexOf(`
453
+ `, index);
454
+ index = newline === -1 ? text.length : newline + 1;
455
+ continue;
456
+ }
457
+ if (char === "/" && text[index + 1] === "*") {
458
+ index = skipBlockComment(text, index);
459
+ continue;
460
+ }
461
+ if (char === "$") {
462
+ const dollarEnd = skipDollarQuoted(text, index);
463
+ if (dollarEnd !== -1) {
464
+ index = dollarEnd;
465
+ continue;
466
+ }
467
+ let digits = index + 1;
468
+ while (digits < text.length && text[digits] !== undefined && /[0-9]/.test(text[digits]))
469
+ digits += 1;
470
+ if (digits > index + 1) {
471
+ chunks.push(text.slice(chunkStart, index));
472
+ indices.push(Number(text.slice(index + 1, digits)));
473
+ chunkStart = digits;
474
+ index = digits;
475
+ continue;
476
+ }
477
+ }
478
+ index += 1;
479
+ }
480
+ chunks.push(text.slice(chunkStart));
481
+ const template = Object.assign(chunks, { raw: chunks });
482
+ return { template, indices };
483
+ }
143
484
  function templateChunks(params) {
144
- return params.text.split(/\$\d+/);
485
+ return [...scanPlaceholders(params).template];
486
+ }
487
+ function bindTemplateValues(params) {
488
+ const { text, indices, values } = params;
489
+ return indices.map((placeholder) => {
490
+ if (placeholder < 1 || placeholder > values.length) {
491
+ throw new VibeError5({
492
+ code: "VIBE_VALIDATION",
493
+ message: `bun:sql prepared statement references $${placeholder} but only ${values.length} parameter(s) were given`,
494
+ meta: { text, placeholder, provided: values.length }
495
+ });
496
+ }
497
+ return values[placeholder - 1];
498
+ });
145
499
  }
146
500
  function createTemplateCache(params) {
147
501
  const { max } = params;
@@ -155,15 +509,14 @@ function createTemplateCache(params) {
155
509
  templates.set(text, existing);
156
510
  return existing;
157
511
  }
158
- const chunks = templateChunks({ text });
159
- const template = Object.assign(chunks, { raw: chunks });
512
+ const prepared = scanPlaceholders({ text });
160
513
  if (templates.size >= max) {
161
514
  const oldest = templates.keys().next().value;
162
515
  if (oldest !== undefined)
163
516
  templates.delete(oldest);
164
517
  }
165
- templates.set(text, template);
166
- return template;
518
+ templates.set(text, prepared);
519
+ return prepared;
167
520
  },
168
521
  size() {
169
522
  return templates.size;
@@ -172,7 +525,7 @@ function createTemplateCache(params) {
172
525
  }
173
526
 
174
527
  // packages/adapter-bun/src/transaction-sql.ts
175
- import { VibeError as VibeError4 } from "@vibeorm/schema";
528
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
176
529
  var ISOLATION_LEVEL_SQL = Object.freeze({
177
530
  ReadCommitted: "READ COMMITTED",
178
531
  RepeatableRead: "REPEATABLE READ",
@@ -185,7 +538,7 @@ function beginStatement(params) {
185
538
  function statementTimeoutStatement(params) {
186
539
  const { timeout } = params;
187
540
  if (!Number.isInteger(timeout) || timeout < 0) {
188
- throw new VibeError4({
541
+ throw new VibeError6({
189
542
  code: "VIBE_VALIDATION",
190
543
  message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
191
544
  meta: { timeout }
@@ -211,6 +564,8 @@ function refuseNestedTransactionOptions(params) {
211
564
  passed.push("isolationLevel");
212
565
  if (options.timeout !== undefined)
213
566
  passed.push("timeout");
567
+ if (options.deadline !== undefined)
568
+ passed.push("deadline");
214
569
  if (passed.length === 0)
215
570
  return;
216
571
  const meta = { provider, nested: true };
@@ -218,9 +573,11 @@ function refuseNestedTransactionOptions(params) {
218
573
  meta.isolationLevel = options.isolationLevel;
219
574
  if (options.timeout !== undefined)
220
575
  meta.timeout = options.timeout;
221
- throw new VibeError4({
576
+ if (options.deadline !== undefined)
577
+ meta.deadline = options.deadline;
578
+ throw new VibeError6({
222
579
  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`,
580
+ 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
581
  meta
225
582
  });
226
583
  }
@@ -236,10 +593,19 @@ class CallbackError extends Error {
236
593
  }
237
594
  var DIALECT = "postgres";
238
595
  var PROVIDER = "postgresql";
239
- var WIRE = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
596
+ var BUDGETS = Object.freeze({
597
+ acquireTimeout: true,
598
+ statementTimeout: "all-statements",
599
+ transactionDeadline: "cancel-running-statements"
600
+ });
601
+ var WIRE_TEXT = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
602
+ var WIRE_PARSED = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
240
603
  function bunAdapter(options) {
241
604
  const usePrepared = options?.preparedStatements ?? false;
242
605
  const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });
606
+ let activeWire = WIRE_TEXT;
607
+ let wireSettled = false;
608
+ let wireProbe = null;
243
609
  let instance = null;
244
610
  let rawSession = null;
245
611
  function getSql() {
@@ -258,11 +624,27 @@ function bunAdapter(options) {
258
624
  instance = url === undefined ? new SQL(sqlOptions) : new SQL(url, sqlOptions);
259
625
  return instance;
260
626
  }
627
+ async function probeJsonWire(source) {
628
+ const probe = renderDiagnostic({ dialect: postgresDialect, query: "postgresJsonWire" });
629
+ try {
630
+ const rows = usePrepared ? await source(templates.getTemplate({ text: probe.text }).template) : await source.unsafe(probe.text, []);
631
+ const probed = rows[0]?.probe;
632
+ if (typeof probed === "object" && probed !== null)
633
+ activeWire = WIRE_PARSED;
634
+ } catch {} finally {
635
+ wireSettled = true;
636
+ }
637
+ }
261
638
  async function runExecute(params) {
639
+ if (!wireSettled) {
640
+ wireProbe ??= probeJsonWire(params.source);
641
+ await wireProbe;
642
+ }
262
643
  const values = serializeParams({ values: params.values });
263
644
  if (usePrepared) {
264
- const template = templates.getTemplate({ text: params.text });
265
- return runMapped(() => params.source(template, ...values));
645
+ const prepared = templates.getTemplate({ text: params.text });
646
+ const bound = bindTemplateValues({ text: params.text, indices: prepared.indices, values });
647
+ return runMapped(() => params.source(prepared.template, ...bound));
266
648
  }
267
649
  return runMapped(() => params.source.unsafe(params.text, values));
268
650
  }
@@ -272,61 +654,79 @@ function bunAdapter(options) {
272
654
  return { rows, affectedRows: readAffectedRows({ rows }) };
273
655
  }
274
656
  function createAdapter(params) {
275
- const { source, parentMode, savepointCounter } = params;
657
+ const { source, parentMode, scope, budget } = params;
658
+ async function applyEngineBudget() {
659
+ if (budget === undefined)
660
+ return;
661
+ const ms = engineStatementBudgetMs({ budget });
662
+ if (ms === null)
663
+ return;
664
+ await runMapped(() => source.unsafe(statementTimeoutStatement({ timeout: ms })));
665
+ }
276
666
  async function transactionViaSavepoint(fn, nestedOptions) {
277
667
  refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
668
+ budget?.assertUsable({ stage: "nested" });
278
669
  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
- });
670
+ const driverSavepoint = transactional.savepoint?.bind(transactional);
671
+ if (driverSavepoint !== undefined) {
672
+ return runNestedSavepoint({
673
+ scope,
674
+ body: async (child) => {
287
675
  try {
288
- return await fn(nested);
676
+ return await driverSavepoint(async (savepointSql) => {
677
+ const nested = createAdapter({
678
+ source: savepointSql,
679
+ parentMode: "tx",
680
+ scope: child
681
+ });
682
+ try {
683
+ return await fn(nested);
684
+ } catch (error) {
685
+ throw new CallbackError(error);
686
+ }
687
+ });
289
688
  } catch (error) {
290
- throw new CallbackError(error);
689
+ if (error instanceof CallbackError)
690
+ throw error.inner;
691
+ throw mapBunDriverError({ error });
291
692
  }
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;
693
+ }
694
+ });
311
695
  }
696
+ return runNestedSavepoint({
697
+ scope,
698
+ execute: (statement) => runMapped(() => transactional.unsafe(statement)),
699
+ body: (child) => fn(createAdapter({ source: transactional, parentMode: "tx", scope: child, budget }))
700
+ });
312
701
  }
313
702
  async function transactionViaPool(fn, transactionOptions) {
314
703
  const pool = source;
315
- if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined) {
704
+ validateTransactionDeadline({
705
+ options: transactionOptions,
706
+ support: BUDGETS.transactionDeadline,
707
+ provider: PROVIDER
708
+ });
709
+ if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined && transactionOptions?.deadline === undefined) {
710
+ const budget3 = startTransactionBudget({ provider: PROVIDER });
316
711
  try {
317
- return await pool.begin(async (txSql) => {
712
+ const result = await pool.begin(async (txSql) => {
713
+ const txScope = createSavepointScope();
318
714
  const txAdapter = createAdapter({
319
715
  source: txSql,
320
716
  parentMode: "tx",
321
- savepointCounter: createSavepointCounter()
717
+ scope: txScope,
718
+ budget: budget3
322
719
  });
323
720
  try {
324
- return await fn(txAdapter);
721
+ return await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
325
722
  } catch (error) {
326
723
  throw new CallbackError(error);
327
724
  }
328
725
  });
726
+ budget3.close({ outcome: "committed" });
727
+ return result;
329
728
  } catch (error) {
729
+ budget3.close({ outcome: "rolled-back" });
330
730
  if (error instanceof CallbackError)
331
731
  throw error.inner;
332
732
  throw mapBunDriverError({ error });
@@ -334,22 +734,37 @@ function bunAdapter(options) {
334
734
  }
335
735
  const timeoutText = transactionOptions.timeout === undefined ? undefined : statementTimeoutStatement({ timeout: transactionOptions.timeout });
336
736
  const reserved = await runMapped(() => pool.reserve());
737
+ const budget2 = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
337
738
  try {
338
739
  await runMapped(() => reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })));
339
740
  if (timeoutText !== undefined)
340
741
  await runMapped(() => reserved.unsafe(timeoutText));
742
+ const txScope = createSavepointScope();
341
743
  const txAdapter = createAdapter({
342
744
  source: reserved,
343
745
  parentMode: "tx",
344
- savepointCounter: createSavepointCounter()
746
+ scope: txScope,
747
+ budget: budget2
345
748
  });
346
- const result = await fn(txAdapter);
749
+ const result = await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
750
+ budget2.assertUsable({ stage: "commit" });
347
751
  await runMapped(() => reserved.unsafe("COMMIT"));
752
+ budget2.close({ outcome: "committed" });
348
753
  return result;
349
754
  } catch (error) {
350
755
  try {
351
756
  await reserved.unsafe("ROLLBACK");
352
- } catch {}
757
+ budget2.close({ outcome: "rolled-back" });
758
+ } catch {
759
+ budget2.close({ outcome: "unknown" });
760
+ if (budget2.totalMs !== null && isTransactionDeadlineError(error)) {
761
+ throw transactionOutcomeUnknownError({
762
+ provider: PROVIDER,
763
+ totalMs: budget2.totalMs,
764
+ cause: error
765
+ });
766
+ }
767
+ }
353
768
  throw error;
354
769
  } finally {
355
770
  reserved.release();
@@ -358,11 +773,24 @@ function bunAdapter(options) {
358
773
  return {
359
774
  dialect: DIALECT,
360
775
  provider: PROVIDER,
361
- wire: WIRE,
776
+ budgets: BUDGETS,
777
+ get wire() {
778
+ return activeWire;
779
+ },
362
780
  async execute(execParams) {
781
+ const turn = scopeTurn({ scope });
782
+ if (turn !== undefined)
783
+ await turn;
784
+ budget?.assertUsable({ stage: "statement" });
785
+ await applyEngineBudget();
363
786
  return runExecute({ source, ...execParams });
364
787
  },
365
788
  async executeUnsafe(execParams) {
789
+ const turn = scopeTurn({ scope });
790
+ if (turn !== undefined)
791
+ await turn;
792
+ budget?.assertUsable({ stage: "statement" });
793
+ await applyEngineBudget();
366
794
  return runExecuteUnsafe({ source, ...execParams });
367
795
  },
368
796
  async transaction(fn, transactionOptions) {
@@ -389,13 +817,16 @@ function bunAdapter(options) {
389
817
  return createAdapter({
390
818
  source: getSql(),
391
819
  parentMode: "pool",
392
- savepointCounter: createSavepointCounter()
820
+ scope: createSavepointScope()
393
821
  });
394
822
  }
395
823
  return {
396
824
  dialect: DIALECT,
397
825
  provider: PROVIDER,
398
- wire: WIRE,
826
+ budgets: BUDGETS,
827
+ get wire() {
828
+ return activeWire;
829
+ },
399
830
  async execute(params) {
400
831
  return poolAdapter().execute(params);
401
832
  },
@@ -461,14 +892,21 @@ function readAffectedRows(params) {
461
892
  return rows.length;
462
893
  }
463
894
  export {
895
+ validateTransactionDeadline,
896
+ transactionOutcomeUnknownError,
897
+ transactionDeadlineError,
898
+ transactionClosedError,
464
899
  toPgArrayLiteral,
465
900
  templateChunks,
466
901
  statementTimeoutStatement,
902
+ startTransactionBudget,
467
903
  serializeParamsRaw,
468
904
  serializeParams,
469
905
  refuseNestedTransactionOptions,
470
906
  nextSavepointName,
471
907
  mapBunDriverError,
908
+ isTransactionDeadlineError,
909
+ engineStatementBudgetMs,
472
910
  createTemplateCache,
473
911
  createSavepointCounter,
474
912
  classifyRawTransactionControl,
@@ -478,8 +916,9 @@ export {
478
916
  beginStatement,
479
917
  applyStartupOptions,
480
918
  ISOLATION_LEVEL_SQL,
919
+ DEFAULT_DEADLINE_CLOCK,
481
920
  BUN_SQLSTATE_REASONS,
482
921
  BUN_SQLSTATE_ERROR_CODES
483
922
  };
484
923
 
485
- //# debugId=08E3C28E82D7C93A64756E2164756E21
924
+ //# debugId=86F466725BC7074B64756E2164756E21