@vibeorm/adapter-pg 1.3.0 → 2.0.0-alpha.10

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 ADDED
@@ -0,0 +1,686 @@
1
+ // @bun
2
+ // packages/adapter-pg/src/index.ts
3
+ import pg from "pg";
4
+
5
+ // packages/adapter-pg/src/errors.ts
6
+ import { VibeError } from "@vibeorm/schema";
7
+ var PG_SQLSTATE_ERROR_CODES = Object.freeze({
8
+ "23505": "VIBE_UNIQUE_VIOLATION",
9
+ "23503": "VIBE_FK_VIOLATION",
10
+ "23502": "VIBE_VALIDATION",
11
+ "23514": "VIBE_VALIDATION",
12
+ "22P02": "VIBE_VALIDATION",
13
+ "42501": "VIBE_PERMISSION_DENIED",
14
+ "40001": "VIBE_TRANSACTION",
15
+ "40P01": "VIBE_TRANSACTION",
16
+ "57014": "VIBE_TRANSACTION"
17
+ });
18
+ var PG_SQLSTATE_REASONS = Object.freeze({
19
+ "57014": "timeout"
20
+ });
21
+ var SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
22
+ function readStringField(params) {
23
+ const value = params.source[params.key];
24
+ return typeof value === "string" && value.length > 0 ? value : undefined;
25
+ }
26
+ function pgErrorSqlstate(params) {
27
+ const { error } = params;
28
+ if (error === null || typeof error !== "object")
29
+ return;
30
+ const code = readStringField({ source: error, key: "code" });
31
+ return code !== undefined && SQLSTATE_PATTERN.test(code) ? code : undefined;
32
+ }
33
+ function mapPgDriverError(params) {
34
+ const { error } = params;
35
+ if (error instanceof VibeError)
36
+ return error;
37
+ const source = error !== null && typeof error === "object" ? error : {};
38
+ const sqlstate = pgErrorSqlstate({ error });
39
+ const code = (sqlstate === undefined ? undefined : PG_SQLSTATE_ERROR_CODES[sqlstate]) ?? "VIBE_ADAPTER";
40
+ const meta = {};
41
+ if (sqlstate !== undefined)
42
+ meta.sqlstate = sqlstate;
43
+ const constraint = readStringField({ source, key: "constraint" });
44
+ if (constraint !== undefined)
45
+ meta.constraint = constraint;
46
+ const table = readStringField({ source, key: "table" });
47
+ if (table !== undefined)
48
+ meta.table = table;
49
+ const column = readStringField({ source, key: "column" });
50
+ if (column !== undefined)
51
+ meta.column = column;
52
+ const reason = sqlstate === undefined ? undefined : PG_SQLSTATE_REASONS[sqlstate];
53
+ if (reason !== undefined)
54
+ meta.reason = reason;
55
+ const message = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
56
+ return new VibeError({ code, message, meta, cause: error });
57
+ }
58
+ async function runMapped(fn) {
59
+ try {
60
+ return await fn();
61
+ } catch (error) {
62
+ throw mapPgDriverError({ error });
63
+ }
64
+ }
65
+
66
+ // packages/adapter-pg/src/params.ts
67
+ class PgArrayParam {
68
+ values;
69
+ constructor(values) {
70
+ this.values = values;
71
+ }
72
+ }
73
+ function isDriverNativeValue(value) {
74
+ return value === null || value === undefined || typeof value !== "object" || value instanceof Date || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
75
+ }
76
+ function serializeParams(params) {
77
+ return params.values.map((value) => {
78
+ if (value instanceof PgArrayParam)
79
+ return value.values;
80
+ if (isDriverNativeValue(value))
81
+ return value;
82
+ return JSON.stringify(value);
83
+ });
84
+ }
85
+ function serializeParamsRaw(params) {
86
+ return params.values.map((value) => {
87
+ if (value instanceof PgArrayParam)
88
+ return value.values;
89
+ if (Array.isArray(value))
90
+ return value;
91
+ if (isDriverNativeValue(value))
92
+ return value;
93
+ return JSON.stringify(value);
94
+ });
95
+ }
96
+
97
+ // packages/adapter-pg/src/savepoint-gate.ts
98
+ import { AsyncLocalStorage } from "async_hooks";
99
+
100
+ // packages/adapter-pg/src/savepoints.ts
101
+ function createSavepointCounter() {
102
+ return { n: 0 };
103
+ }
104
+ function nextSavepointName(params) {
105
+ return `vibeorm_sp_${params.counter.n++}`;
106
+ }
107
+
108
+ // packages/adapter-pg/src/savepoint-gate.ts
109
+ var CURRENT = new AsyncLocalStorage;
110
+ function createSavepointScope() {
111
+ return { depth: 0, state: { counter: createSavepointCounter(), open: null } };
112
+ }
113
+ function runRootScope(params) {
114
+ return CURRENT.run(params.scope, params.body);
115
+ }
116
+ function scopeTurn(params) {
117
+ const open = params.scope.state.open;
118
+ if (mayProceed({ scope: params.scope, open }))
119
+ return;
120
+ return waitForTurn({ scope: params.scope });
121
+ }
122
+ async function runNestedSavepoint(params) {
123
+ const { scope, execute, body } = params;
124
+ const node = await acquire({ scope });
125
+ try {
126
+ if (execute !== undefined)
127
+ await execute(`SAVEPOINT ${node.name}`);
128
+ let result;
129
+ try {
130
+ result = await CURRENT.run(node.scope, () => body(node.scope));
131
+ } catch (error) {
132
+ await drainDescendants({ node });
133
+ if (execute !== undefined) {
134
+ try {
135
+ await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);
136
+ } catch {}
137
+ }
138
+ throw error;
139
+ }
140
+ await drainDescendants({ node });
141
+ if (execute !== undefined)
142
+ await execute(`RELEASE SAVEPOINT ${node.name}`);
143
+ return result;
144
+ } finally {
145
+ closeNode({ node });
146
+ }
147
+ }
148
+ function mayProceed(params) {
149
+ const { scope, open } = params;
150
+ if (open === null)
151
+ return true;
152
+ if (open.scope === scope)
153
+ return true;
154
+ if (open.scope.depth < scope.depth)
155
+ return true;
156
+ return CURRENT.getStore() === open.scope;
157
+ }
158
+ async function waitForTurn(params) {
159
+ for (;; ) {
160
+ const open = params.scope.state.open;
161
+ if (open === null || mayProceed({ scope: params.scope, open }))
162
+ return;
163
+ await open.settled;
164
+ }
165
+ }
166
+ async function acquire(params) {
167
+ const { scope } = params;
168
+ for (;; ) {
169
+ const open = scope.state.open;
170
+ if (mayProceed({ scope, open }))
171
+ return pushNode({ scope, parent: open });
172
+ await open.settled;
173
+ }
174
+ }
175
+ function pushNode(params) {
176
+ const { scope, parent } = params;
177
+ const state = scope.state;
178
+ let settle = () => {
179
+ return;
180
+ };
181
+ const settled = new Promise((resolve) => {
182
+ settle = () => resolve();
183
+ });
184
+ const node = {
185
+ scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },
186
+ name: nextSavepointName({ counter: state.counter }),
187
+ parent,
188
+ settled,
189
+ settle
190
+ };
191
+ state.open = node;
192
+ return node;
193
+ }
194
+ function closeNode(params) {
195
+ const { node } = params;
196
+ const state = node.scope.state;
197
+ if (state.open === node)
198
+ state.open = node.parent;
199
+ node.settle();
200
+ }
201
+ async function drainDescendants(params) {
202
+ const state = params.node.scope.state;
203
+ for (;; ) {
204
+ const open = state.open;
205
+ if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth)
206
+ return;
207
+ await open.settled;
208
+ }
209
+ }
210
+
211
+ // packages/adapter-pg/src/statement-cache.ts
212
+ function createStatementNameCache(params) {
213
+ const max = Math.max(0, params.max);
214
+ const names = new Map;
215
+ let counter = 0;
216
+ return {
217
+ getName(nameParams) {
218
+ const { text } = nameParams;
219
+ const existing = names.get(text);
220
+ if (existing !== undefined)
221
+ return existing;
222
+ if (names.size >= max)
223
+ return;
224
+ const name = `vibeorm_${counter++}`;
225
+ names.set(text, name);
226
+ return name;
227
+ },
228
+ size() {
229
+ return names.size;
230
+ }
231
+ };
232
+ }
233
+
234
+ // packages/adapter-pg/src/transaction-budget.ts
235
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
236
+ var DEFAULT_DEADLINE_CLOCK = () => Date.now();
237
+ function validateTransactionDeadline(params) {
238
+ const { options, support, provider } = params;
239
+ const deadline = options?.deadline;
240
+ if (deadline === undefined)
241
+ return;
242
+ if (support === "unsupported") {
243
+ throw new VibeError2({
244
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
245
+ message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
246
+ meta: { provider, feature: "transactionDeadline", supported: support }
247
+ });
248
+ }
249
+ if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
250
+ throw new VibeError2({
251
+ code: "VIBE_VALIDATION",
252
+ message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
253
+ meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
254
+ });
255
+ }
256
+ const requested = deadline.enforcement ?? "between-statements";
257
+ if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
258
+ throw new VibeError2({
259
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
260
+ 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',
261
+ meta: { provider, feature: "transactionDeadline", requested, supported: support }
262
+ });
263
+ }
264
+ }
265
+ function startTransactionBudget(params) {
266
+ const { options, provider } = params;
267
+ const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;
268
+ const deadline = options?.deadline;
269
+ const totalMs = deadline === undefined ? null : deadline.totalMs;
270
+ const enforcement = deadline === undefined ? null : deadline.enforcement ?? "between-statements";
271
+ const expiresAt = totalMs === null ? null : clock() + totalMs;
272
+ let outcome = null;
273
+ const budget = {
274
+ enforcement,
275
+ totalMs,
276
+ statementTimeoutMs: options?.timeout ?? null,
277
+ remainingMs() {
278
+ return expiresAt === null ? null : Math.max(0, expiresAt - clock());
279
+ },
280
+ expired() {
281
+ return expiresAt !== null && clock() >= expiresAt;
282
+ },
283
+ assertUsable(assertParams) {
284
+ if (outcome !== null) {
285
+ throw transactionClosedError({ provider, outcome, stage: assertParams.stage });
286
+ }
287
+ if (expiresAt !== null && clock() >= expiresAt) {
288
+ throw transactionDeadlineError({
289
+ provider,
290
+ stage: assertParams.stage,
291
+ totalMs,
292
+ overdueMs: clock() - expiresAt
293
+ });
294
+ }
295
+ },
296
+ close(closeParams) {
297
+ if (outcome === null)
298
+ outcome = closeParams.outcome;
299
+ },
300
+ get outcome() {
301
+ return outcome;
302
+ }
303
+ };
304
+ return budget;
305
+ }
306
+ function engineStatementBudgetMs(params) {
307
+ const { budget } = params;
308
+ if (budget.enforcement !== "cancel-running-statements")
309
+ return null;
310
+ const remaining = budget.remainingMs();
311
+ if (remaining === null)
312
+ return null;
313
+ const totalBound = Math.max(1, Math.ceil(remaining));
314
+ return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0 ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;
315
+ }
316
+ function transactionDeadlineError(params) {
317
+ const { provider, stage, totalMs, overdueMs } = params;
318
+ const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
319
+ return new VibeError2({
320
+ code: "VIBE_TRANSACTION",
321
+ 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.",
322
+ meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
323
+ });
324
+ }
325
+ function transactionClosedError(params) {
326
+ const { provider, outcome, stage } = params;
327
+ return new VibeError2({
328
+ code: "VIBE_TRANSACTION",
329
+ 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",
330
+ meta: { provider, reason: "transaction-closed", outcome, stage }
331
+ });
332
+ }
333
+ function transactionOutcomeUnknownError(params) {
334
+ const { provider, totalMs, cause } = params;
335
+ return new VibeError2({
336
+ code: "VIBE_TRANSACTION",
337
+ 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.",
338
+ meta: {
339
+ provider,
340
+ reason: "transaction-outcome-unknown",
341
+ outcome: "unknown",
342
+ totalMs,
343
+ rollbackFailed: true
344
+ },
345
+ cause
346
+ });
347
+ }
348
+ function isTransactionDeadlineError(error) {
349
+ const reason = error?.meta?.reason;
350
+ return reason === "transaction-deadline";
351
+ }
352
+
353
+ // packages/adapter-pg/src/transaction-sql.ts
354
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
355
+ var ISOLATION_LEVEL_SQL = Object.freeze({
356
+ ReadCommitted: "READ COMMITTED",
357
+ RepeatableRead: "REPEATABLE READ",
358
+ Serializable: "SERIALIZABLE"
359
+ });
360
+ function beginStatement(params) {
361
+ const { isolationLevel } = params;
362
+ return isolationLevel === undefined ? "BEGIN" : `BEGIN ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`;
363
+ }
364
+ function statementTimeoutStatement(params) {
365
+ const { timeout } = params;
366
+ if (!Number.isInteger(timeout) || timeout < 0) {
367
+ throw new VibeError3({
368
+ code: "VIBE_VALIDATION",
369
+ message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
370
+ meta: { timeout }
371
+ });
372
+ }
373
+ return `SET LOCAL statement_timeout = ${timeout}`;
374
+ }
375
+ var RAW_OPEN_PATTERN = /^\s*(?:begin|start\s+transaction)\b[^;]*;?\s*$/i;
376
+ var RAW_CLOSE_PATTERN = /^\s*(?:commit|end|rollback|abort)(?:\s+(?:transaction|work))?\s*;?\s*$/i;
377
+ function classifyRawTransactionControl(params) {
378
+ if (RAW_OPEN_PATTERN.test(params.text))
379
+ return "open";
380
+ if (RAW_CLOSE_PATTERN.test(params.text))
381
+ return "close";
382
+ return null;
383
+ }
384
+ function refuseNestedTransactionOptions(params) {
385
+ const { options, provider } = params;
386
+ if (options === undefined)
387
+ return;
388
+ const passed = [];
389
+ if (options.isolationLevel !== undefined)
390
+ passed.push("isolationLevel");
391
+ if (options.timeout !== undefined)
392
+ passed.push("timeout");
393
+ if (options.deadline !== undefined)
394
+ passed.push("deadline");
395
+ if (passed.length === 0)
396
+ return;
397
+ const meta = { provider, nested: true };
398
+ if (options.isolationLevel !== undefined)
399
+ meta.isolationLevel = options.isolationLevel;
400
+ if (options.timeout !== undefined)
401
+ meta.timeout = options.timeout;
402
+ if (options.deadline !== undefined)
403
+ meta.deadline = options.deadline;
404
+ throw new VibeError3({
405
+ code: "VIBE_VALIDATION",
406
+ 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`,
407
+ meta
408
+ });
409
+ }
410
+
411
+ // packages/adapter-pg/src/index.ts
412
+ var POOL_STATEMENT_NAMES = new WeakMap;
413
+ function guardBackgroundErrors(params) {
414
+ const { client } = params;
415
+ if (typeof client.on !== "function")
416
+ return () => {
417
+ return;
418
+ };
419
+ const swallow = () => {};
420
+ client.on("error", swallow);
421
+ return () => {
422
+ client.removeListener?.("error", swallow);
423
+ };
424
+ }
425
+ var DIALECT = "postgres";
426
+ var PROVIDER = "postgresql";
427
+ var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
428
+ var BUDGETS = Object.freeze({
429
+ acquireTimeout: true,
430
+ statementTimeout: "all-statements",
431
+ transactionDeadline: "cancel-running-statements"
432
+ });
433
+ var NUMERIC_ARRAY_OID = 1231;
434
+ var TEXT_ARRAY_OID = 1009;
435
+ var EXACT_TYPES = {
436
+ getTypeParser(oid, format) {
437
+ if (oid === NUMERIC_ARRAY_OID && (format === undefined || format === "text")) {
438
+ return driverTypeParser(TEXT_ARRAY_OID, "text");
439
+ }
440
+ return driverTypeParser(oid, format);
441
+ }
442
+ };
443
+ var driverTypeParser = pg.types.getTypeParser;
444
+ function pgAdapter(options) {
445
+ const usePrepared = options?.preparedStatements ?? false;
446
+ const stmtCacheMax = options?.stmtCacheMax ?? 1000;
447
+ let pool = null;
448
+ let ownsPool = false;
449
+ let rawSession = null;
450
+ let rawSessionUnguard = null;
451
+ function getPool() {
452
+ if (pool !== null)
453
+ return pool;
454
+ if (options?.pool !== undefined) {
455
+ pool = options.pool;
456
+ ownsPool = false;
457
+ return pool;
458
+ }
459
+ const config = {
460
+ connectionString: options?.connectionString ?? process.env.DATABASE_URL,
461
+ max: options?.max ?? 10
462
+ };
463
+ if (options?.statementTimeout !== undefined) {
464
+ config.statement_timeout = options.statementTimeout;
465
+ }
466
+ if (options?.connectionTimeout !== undefined) {
467
+ config.connectionTimeoutMillis = options.connectionTimeout;
468
+ }
469
+ const PoolCtor = pg.Pool;
470
+ pool = new PoolCtor(config);
471
+ pool.on?.("error", () => {});
472
+ ownsPool = true;
473
+ return pool;
474
+ }
475
+ function statementNamesFor(params) {
476
+ const existing = POOL_STATEMENT_NAMES.get(params.pool);
477
+ if (existing !== undefined)
478
+ return existing;
479
+ const created = createStatementNameCache({ max: stmtCacheMax });
480
+ POOL_STATEMENT_NAMES.set(params.pool, created);
481
+ return created;
482
+ }
483
+ function buildQueryConfig(params) {
484
+ const values = serializeParams({ values: params.values });
485
+ if (!usePrepared)
486
+ return { text: params.text, values, types: EXACT_TYPES };
487
+ const name = statementNamesFor({ pool: getPool() }).getName({ text: params.text });
488
+ return name === undefined ? { text: params.text, values, types: EXACT_TYPES } : { name, text: params.text, values, types: EXACT_TYPES };
489
+ }
490
+ async function runExecute(params) {
491
+ const config = buildQueryConfig({ text: params.text, values: params.values });
492
+ const result = await runMapped(() => params.queryable.query(config));
493
+ return result.rows;
494
+ }
495
+ async function runExecuteUnsafe(params) {
496
+ const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
497
+ const result = await runMapped(() => params.queryable.query({ text: params.text, values, types: EXACT_TYPES }));
498
+ return { rows: result.rows, affectedRows: result.rowCount ?? 0 };
499
+ }
500
+ function createClientAdapter(params) {
501
+ const { client, scope, budget } = params;
502
+ async function applyEngineBudget() {
503
+ const ms = engineStatementBudgetMs({ budget });
504
+ if (ms === null)
505
+ return;
506
+ await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));
507
+ }
508
+ return {
509
+ dialect: DIALECT,
510
+ provider: PROVIDER,
511
+ wire: WIRE,
512
+ budgets: BUDGETS,
513
+ async execute(execParams) {
514
+ const turn = scopeTurn({ scope });
515
+ if (turn !== undefined)
516
+ await turn;
517
+ budget.assertUsable({ stage: "statement" });
518
+ await applyEngineBudget();
519
+ return runExecute({ queryable: client, ...execParams });
520
+ },
521
+ async executeUnsafe(execParams) {
522
+ const turn = scopeTurn({ scope });
523
+ if (turn !== undefined)
524
+ await turn;
525
+ budget.assertUsable({ stage: "statement" });
526
+ await applyEngineBudget();
527
+ return runExecuteUnsafe({ queryable: client, ...execParams });
528
+ },
529
+ async transaction(fn, nestedOptions) {
530
+ refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
531
+ budget.assertUsable({ stage: "nested" });
532
+ return runNestedSavepoint({
533
+ scope,
534
+ execute: (statement) => runMapped(() => client.query({ text: statement })),
535
+ body: (child) => fn(createClientAdapter({ client, scope: child, budget }))
536
+ });
537
+ },
538
+ async connect() {},
539
+ async disconnect() {},
540
+ formatArrayParam(values) {
541
+ return new PgArrayParam(values);
542
+ }
543
+ };
544
+ }
545
+ return {
546
+ dialect: DIALECT,
547
+ provider: PROVIDER,
548
+ wire: WIRE,
549
+ budgets: BUDGETS,
550
+ async execute(params) {
551
+ return runExecute({ queryable: getPool(), ...params });
552
+ },
553
+ async executeUnsafe(params) {
554
+ const control = classifyRawTransactionControl({ text: params.text });
555
+ if (rawSession !== null) {
556
+ if (control === "close") {
557
+ const session = rawSession;
558
+ const unguard = rawSessionUnguard;
559
+ rawSession = null;
560
+ rawSessionUnguard = null;
561
+ try {
562
+ return await runExecuteUnsafe({ queryable: session, ...params });
563
+ } finally {
564
+ unguard?.();
565
+ session.release();
566
+ }
567
+ }
568
+ return runExecuteUnsafe({ queryable: rawSession, ...params });
569
+ }
570
+ if (control === "open") {
571
+ const client = await runMapped(() => getPool().connect());
572
+ const unguard = guardBackgroundErrors({ client });
573
+ try {
574
+ const result = await runExecuteUnsafe({ queryable: client, ...params });
575
+ rawSession = client;
576
+ rawSessionUnguard = unguard;
577
+ return result;
578
+ } catch (error) {
579
+ unguard();
580
+ client.release();
581
+ throw error;
582
+ }
583
+ }
584
+ return runExecuteUnsafe({ queryable: getPool(), ...params });
585
+ },
586
+ async transaction(fn, transactionOptions) {
587
+ validateTransactionDeadline({
588
+ options: transactionOptions,
589
+ support: BUDGETS.transactionDeadline,
590
+ provider: PROVIDER
591
+ });
592
+ const client = await runMapped(() => getPool().connect());
593
+ const unguard = guardBackgroundErrors({ client });
594
+ const scope = createSavepointScope();
595
+ const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
596
+ let discard = false;
597
+ try {
598
+ await runMapped(() => client.query({ text: beginStatement({ isolationLevel: transactionOptions?.isolationLevel }) }));
599
+ if (transactionOptions?.timeout !== undefined) {
600
+ const text = statementTimeoutStatement({ timeout: transactionOptions.timeout });
601
+ await runMapped(() => client.query({ text }));
602
+ }
603
+ const result = await runRootScope({
604
+ scope,
605
+ body: () => fn(createClientAdapter({ client, scope, budget }))
606
+ });
607
+ budget.assertUsable({ stage: "commit" });
608
+ await runMapped(() => client.query({ text: "COMMIT" }));
609
+ budget.close({ outcome: "committed" });
610
+ return result;
611
+ } catch (error) {
612
+ try {
613
+ await client.query({ text: "ROLLBACK" });
614
+ budget.close({ outcome: "rolled-back" });
615
+ } catch {
616
+ discard = true;
617
+ budget.close({ outcome: "unknown" });
618
+ if (budget.totalMs !== null && isTransactionDeadlineError(error)) {
619
+ throw transactionOutcomeUnknownError({
620
+ provider: PROVIDER,
621
+ totalMs: budget.totalMs,
622
+ cause: error
623
+ });
624
+ }
625
+ }
626
+ throw error;
627
+ } finally {
628
+ unguard();
629
+ client.release(discard);
630
+ }
631
+ },
632
+ async connect() {
633
+ const client = await runMapped(() => getPool().connect());
634
+ client.release();
635
+ },
636
+ async disconnect() {
637
+ if (rawSession !== null) {
638
+ const session = rawSession;
639
+ const unguard = rawSessionUnguard;
640
+ rawSession = null;
641
+ rawSessionUnguard = null;
642
+ try {
643
+ await session.query({ text: "ROLLBACK" });
644
+ } catch {}
645
+ unguard?.();
646
+ session.release();
647
+ }
648
+ if (pool !== null && ownsPool) {
649
+ const closing = pool;
650
+ pool = null;
651
+ await runMapped(() => closing.end());
652
+ }
653
+ },
654
+ formatArrayParam(values) {
655
+ return new PgArrayParam(values);
656
+ }
657
+ };
658
+ }
659
+ export {
660
+ validateTransactionDeadline,
661
+ transactionOutcomeUnknownError,
662
+ transactionDeadlineError,
663
+ transactionClosedError,
664
+ statementTimeoutStatement,
665
+ startTransactionBudget,
666
+ serializeParamsRaw,
667
+ serializeParams,
668
+ refuseNestedTransactionOptions,
669
+ pgErrorSqlstate,
670
+ pgAdapter,
671
+ nextSavepointName,
672
+ mapPgDriverError,
673
+ isTransactionDeadlineError,
674
+ engineStatementBudgetMs,
675
+ createStatementNameCache,
676
+ createSavepointCounter,
677
+ classifyRawTransactionControl,
678
+ beginStatement,
679
+ PgArrayParam,
680
+ PG_SQLSTATE_REASONS,
681
+ PG_SQLSTATE_ERROR_CODES,
682
+ ISOLATION_LEVEL_SQL,
683
+ DEFAULT_DEADLINE_CLOCK
684
+ };
685
+
686
+ //# debugId=17E44C1AF7E3C0D164756E2164756E21