kern-db 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1470 @@
1
+ import { i as decodeBoolean } from "./bytea-DNZ_ftio.mjs";
2
+ import { S as createStatement, _ as sql, f as compileFragment, n as startSqlQuery, p as compileScript, x as quoteIdentifier } from "./query-FKBN7BMY.mjs";
3
+ import { i as setBrand, n as QueryError, r as isInstance, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
4
+ import { n as formatArray } from "./array-BLt8ZNR5.mjs";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+ // src/transaction/release.ts
7
+ /**
8
+ * Returns a connection to the driver and passes a failed release to `onReleaseError`, or to `console.error` without one.
9
+ *
10
+ * The result or error of the transaction is decided before its release, so a failed release must not change it: a committed transaction that rejected would be retried.
11
+ * Reporting the failure cannot change it either.
12
+ * The error of a handler that throws or rejects goes to `console.error` with the failure of the release, and a `console.error` that throws is ignored.
13
+ */
14
+ async function releaseConnection(connection, onReleaseError) {
15
+ try {
16
+ await connection.release();
17
+ } catch (error) {
18
+ reportReleaseError(error, onReleaseError);
19
+ }
20
+ }
21
+ /** Passes a failed release to `onReleaseError`, or to `console.error` without one, without throwing. */
22
+ function reportReleaseError(error, onReleaseError) {
23
+ if (!onReleaseError) {
24
+ logReleaseError(error);
25
+ return;
26
+ }
27
+ try {
28
+ Promise.resolve(onReleaseError(error)).catch((handlerError) => logReleaseError(error, { error: handlerError }));
29
+ } catch (handlerError) {
30
+ logReleaseError(error, { error: handlerError });
31
+ }
32
+ }
33
+ /**
34
+ * Writes a failed release to `console.error`, preceded by the error of `onReleaseError` when the handler failed.
35
+ *
36
+ * A console that throws, such as one redirected to a failing logger, leaves nowhere to report to, so its error is ignored.
37
+ */
38
+ function logReleaseError(error, handlerFailure) {
39
+ try {
40
+ if (handlerFailure) console.error("kern-db: onReleaseError failed", handlerFailure.error);
41
+ console.error("kern-db: releasing a connection failed", error);
42
+ } catch {}
43
+ }
44
+ // src/transaction/abort.ts
45
+ /**
46
+ * Runs `start` and settles as its promise, or rejects with the reason of `signal` when it aborts first and `abort`, called on the abort, returns `true`.
47
+ *
48
+ * It listens from before `start`, which may abort the signal before it returns, and stops once the promise of `start` settles, such as for the signal that `node:test` aborts after every test.
49
+ * A later rejection of that promise stays handled, and a value it resolves with after the rejection goes to `discard`, which handles its own failures.
50
+ */
51
+ function settleUnlessAborted(start, signal, abort, discard) {
52
+ const settled = Promise.withResolvers();
53
+ let abandoned = false;
54
+ const onAbort = () => {
55
+ if (abort()) {
56
+ abandoned = true;
57
+ settled.reject(signal.reason);
58
+ }
59
+ };
60
+ signal.addEventListener("abort", onAbort, { once: true });
61
+ let running;
62
+ try {
63
+ running = start();
64
+ } catch (error) {
65
+ signal.removeEventListener("abort", onAbort);
66
+ settled.reject(error);
67
+ return settled.promise;
68
+ }
69
+ running.then((value) => {
70
+ signal.removeEventListener("abort", onAbort);
71
+ if (abandoned) discard?.(value);
72
+ else settled.resolve(value);
73
+ }, (error) => {
74
+ signal.removeEventListener("abort", onAbort);
75
+ settled.reject(error);
76
+ });
77
+ return settled.promise;
78
+ }
79
+ /**
80
+ * Acquires a connection for a test transaction, rejecting with the reason of `signal` when it aborts first.
81
+ *
82
+ * A connection acquired after the abort returns to the pool as best-effort cleanup: the test transaction has already rejected, so nothing awaits the release, and its failure goes to `onReleaseError`.
83
+ */
84
+ function acquireUnlessAborted(driver, signal, onReleaseError) {
85
+ if (signal.aborted) return Promise.reject(signal.reason);
86
+ return settleUnlessAborted(() => driver.acquire(), signal, () => true, (connection) => void releaseConnection(connection, onReleaseError));
87
+ }
88
+ // src/transaction/advisory-lock.ts
89
+ /** Pause of `pollLock` in milliseconds after its first attempt, which doubles after each attempt up to `maxPollDelay`. */
90
+ const firstPollDelay = 50;
91
+ /** Longest pause of `pollLock` in milliseconds. */
92
+ const maxPollDelay = 1e3;
93
+ /** Creates the statement that takes the transaction-level advisory lock of `key`, waiting while another transaction or session holds it. */
94
+ function createLockStatement(key) {
95
+ return createStatement("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
96
+ }
97
+ /** Creates the statement that takes the transaction-level advisory lock of `key` unless another transaction or session holds it, see `isTaken`. */
98
+ function createTryLockStatement(key) {
99
+ return createStatement("SELECT pg_try_advisory_xact_lock(hashtextextended($1, 0))", [key]);
100
+ }
101
+ /** Tells whether the statement that tried to take a lock took it. */
102
+ function isTaken(result) {
103
+ return decodeBoolean(result.rows[0][0]);
104
+ }
105
+ /**
106
+ * Calls `tryLock` until it takes the lock, with `wait` pausing between the attempts, and rejects with the reason of `signal` once it aborts.
107
+ *
108
+ * An attempt that runs when `signal` aborts is awaited, and if it takes the lock, the call resolves, since the session holds the lock until it ends.
109
+ * `wait` is `pause`, which a test replaces to observe the pauses without a clock.
110
+ */
111
+ async function pollLock(tryLock, signal, wait = pause) {
112
+ signal?.throwIfAborted();
113
+ let delay = firstPollDelay;
114
+ while (!await tryLock()) {
115
+ signal?.throwIfAborted();
116
+ await wait(delay, signal);
117
+ delay = Math.min(delay * 2, maxPollDelay);
118
+ }
119
+ }
120
+ /** Waits `delay` milliseconds, or rejects with the reason of `signal` once it aborts, clearing the timer so that it keeps no process alive. */
121
+ function pause(delay, signal) {
122
+ return new Promise((resolve, reject) => {
123
+ const onAbort = () => {
124
+ clearTimeout(timer);
125
+ reject(signal.reason);
126
+ };
127
+ const timer = setTimeout(() => {
128
+ signal?.removeEventListener("abort", onAbort);
129
+ resolve();
130
+ }, delay);
131
+ signal?.addEventListener("abort", onAbort, { once: true });
132
+ });
133
+ }
134
+ /**
135
+ * Advisory locks that a session takes with `tryLock` until it ends.
136
+ *
137
+ * PostgreSQL holds a lock of a session until `pg_advisory_unlock` releases it as many times as the session took it, so the session takes each key once, and one `pg_advisory_unlock` of each key releases it.
138
+ */
139
+ var SessionLocks = class {
140
+ /** Attempt of each key that the session holds or is taking; an attempt that does not take its lock leaves it. */
141
+ #attempts = /* @__PURE__ */ new Map();
142
+ /** Whether a statement of an attempt failed: PostgreSQL keeps a lock of a session that it took after the statement fails, so whether the connection holds it is unknown. */
143
+ #failed = false;
144
+ /** Takes the lock of `key` on `connection` unless another session or transaction holds it, and tells whether it took it; a key that the session holds or is taking returns that attempt without a statement. */
145
+ take(connection, key) {
146
+ const current = this.#attempts.get(key);
147
+ if (current) return current;
148
+ const attempt = connection.execute(createStatement("SELECT pg_try_advisory_lock(hashtextextended($1, 0))", [key])).then((result) => {
149
+ const taken = isTaken(result);
150
+ if (!taken) this.#attempts.delete(key);
151
+ return taken;
152
+ }, (error) => {
153
+ this.#failed = true;
154
+ this.#attempts.delete(key);
155
+ throw error;
156
+ });
157
+ this.#attempts.set(key, attempt);
158
+ return attempt;
159
+ }
160
+ /**
161
+ * Waits for the attempts in progress and returns the keys of the locks that the session holds, or `undefined` when a statement of an attempt failed, so that it is unknown which locks the connection holds.
162
+ *
163
+ * An attempt that the callback of the session did not await is sent before the session ends, so waiting for it does not wait for the session.
164
+ */
165
+ async settle() {
166
+ await Promise.allSettled(this.#attempts.values());
167
+ return this.#failed ? void 0 : [...this.#attempts.keys()];
168
+ }
169
+ };
170
+ /**
171
+ * Creates the statement that releases the locks of a session of `keys`, once each, see `SessionLocks`.
172
+ *
173
+ * It takes the keys as one array and releases them in rows, since PostgreSQL limits the columns of a result to 1664.
174
+ */
175
+ function createUnlockStatement(keys) {
176
+ return createStatement("SELECT pg_advisory_unlock(hashtextextended(key, 0)) FROM unnest($1::text[]) AS key", [formatArray(keys, (key) => key)]);
177
+ }
178
+ // src/transaction/connection-loss.ts
179
+ /** Marks an instance of `TransactionConnectionError` of any copy of kern-db. */
180
+ const transactionConnectionErrorBrand = /* @__PURE__ */ Symbol.for("kern-db.TransactionConnectionError");
181
+ /** Message of a transaction whose connection failed in each phase. */
182
+ const phaseMessages = {
183
+ begin: "Transaction did not start: its connection failed",
184
+ transaction: "Transaction was rolled back: its connection failed",
185
+ commit: "Transaction may have been committed: its connection failed during COMMIT"
186
+ };
187
+ /**
188
+ * Error of a transaction whose connection failed, which tells whether the transaction may have been committed.
189
+ *
190
+ * Its `cause` is the error that the transaction would reject with otherwise, such as the error of the callback, or a `SuppressedError` of a failed rollback that keeps it.
191
+ */
192
+ var TransactionConnectionError = class TransactionConnectionError extends ConnectionError {
193
+ name = "TransactionConnectionError";
194
+ /**
195
+ * Phase of the transaction in which its connection failed:
196
+ *
197
+ * - `begin`: before the callback, such as getting the connection or `BEGIN`, so the transaction applied nothing;
198
+ * - `transaction`: in the callback or its rollback, so the transaction was rolled back;
199
+ * - `commit`: during `COMMIT`, so whether the transaction was committed is unknown.
200
+ */
201
+ phase;
202
+ constructor(phase, options) {
203
+ super(phaseMessages[phase], options);
204
+ this.phase = phase;
205
+ setBrand(this, transactionConnectionErrorBrand);
206
+ }
207
+ /** Tells whether `value` is an instance of the class, including one of `TransactionConnectionError` of another copy of kern-db. */
208
+ static [Symbol.hasInstance](value) {
209
+ return isInstance(this, TransactionConnectionError, transactionConnectionErrorBrand, value);
210
+ }
211
+ };
212
+ /**
213
+ * Reserved connection of a transaction of `transaction` that records the first `ConnectionError` of its statements and scripts, including those of its nested levels.
214
+ *
215
+ * A failure before `COMMIT` is sent makes the transaction roll back rather than commit, since the statement may not have run.
216
+ * A transaction that rejects after a failure rejects with `TransactionConnectionError` of its phase, while one whose `COMMIT` succeeded resolves.
217
+ */
218
+ var ConnectionWatch = class {
219
+ /** Phase of the transaction, which its root advances to `transaction` before the callback and to `commit` before `COMMIT`. */
220
+ phase = "begin";
221
+ /** First `ConnectionError`; a later statement that succeeds changes nothing. */
222
+ failure;
223
+ #connection;
224
+ #observe = (error) => {
225
+ if (!this.failure && error instanceof ConnectionError) this.failure = {
226
+ error,
227
+ phase: this.phase
228
+ };
229
+ throw error;
230
+ };
231
+ constructor(connection) {
232
+ this.#connection = connection;
233
+ }
234
+ execute(statement) {
235
+ return this.#connection.execute(statement).catch(this.#observe);
236
+ }
237
+ executeScript(text) {
238
+ return this.#connection.executeScript(text).catch(this.#observe);
239
+ }
240
+ release() {
241
+ return this.#connection.release();
242
+ }
243
+ };
244
+ // src/transaction/settings.ts
245
+ /** Settings of a level of `transaction` or of `session` with `options`, or `undefined` when it has none. */
246
+ function getLevelSettings(options) {
247
+ return options?.searchPath ? [{
248
+ name: "search_path",
249
+ value: options.searchPath.map(quoteIdentifier).join(", ")
250
+ }] : void 0;
251
+ }
252
+ /**
253
+ * Applies `settings` to a level whose `BEGIN` or `SAVEPOINT` completed, or to a session, unless its cleanup has started.
254
+ *
255
+ * A savepoint first reads the values they replace, which it restores before its `RELEASE`: PostgreSQL keeps a setting of a released savepoint until the transaction ends, while `ROLLBACK TO` restores it itself.
256
+ * A session reads them too, and sets its settings for the whole session of PostgreSQL, since it has no transaction that would end them; the coordinator restores them before it releases the connection.
257
+ */
258
+ async function applySettings(level, settings) {
259
+ if (level.cleanup) return;
260
+ const local = !level.session;
261
+ if (level.session || level.savepoint) {
262
+ let text = "SELECT";
263
+ const names = [];
264
+ for (const { name } of settings) {
265
+ names.push(name);
266
+ text += `${names.length > 1 ? "," : ""} current_setting($${names.length})`;
267
+ }
268
+ const values = (await level.connection.execute(createStatement(text, names))).rows[0];
269
+ level.restore = createSettingStatement(settings.map(({ name }, index) => ({
270
+ name,
271
+ value: values[index]
272
+ })), local);
273
+ if (level.cleanup) return;
274
+ }
275
+ await level.connection.execute(createSettingStatement(settings, local));
276
+ }
277
+ /** Creates the statement that sets `settings`: when `local`, until the transaction ends or rolls back to an earlier savepoint, as `SET LOCAL` does, and otherwise for the session of PostgreSQL, as `SET` does. */
278
+ function createSettingStatement(settings, local) {
279
+ let text = "SELECT";
280
+ const parameters = [];
281
+ for (const { name, value } of settings) {
282
+ parameters.push(name, value);
283
+ text += `${parameters.length > 2 ? "," : ""} set_config($${parameters.length - 1}, $${parameters.length}, ${local})`;
284
+ }
285
+ return createStatement(text, parameters);
286
+ }
287
+ // src/transaction/level.ts
288
+ /** Options of a level started without options, shared so that such a start allocates none. */
289
+ const noLevelOptions = {};
290
+ const beginStatement$1 = /* @__PURE__ */ createStatement("BEGIN");
291
+ const isolatedBeginStatements = {
292
+ "read committed": /* @__PURE__ */ createStatement("BEGIN ISOLATION LEVEL READ COMMITTED"),
293
+ "repeatable read": /* @__PURE__ */ createStatement("BEGIN ISOLATION LEVEL REPEATABLE READ"),
294
+ serializable: /* @__PURE__ */ createStatement("BEGIN ISOLATION LEVEL SERIALIZABLE")
295
+ };
296
+ const readIsolationStatement = /* @__PURE__ */ createStatement("SELECT current_setting('transaction_isolation')");
297
+ const commitStatement = /* @__PURE__ */ createStatement("COMMIT");
298
+ const rollbackStatement = /* @__PURE__ */ createStatement("ROLLBACK");
299
+ /**
300
+ * Statement that starts the transaction of a level that is not a savepoint, a transaction of `transaction`, a test transaction, or the first test fixture, at `isolation` if given.
301
+ *
302
+ * PostgreSQL takes the isolation level only before the first query of a transaction, so `BEGIN` sets it.
303
+ */
304
+ function getBeginStatement(isolation) {
305
+ return isolation ? isolatedBeginStatements[isolation] : beginStatement$1;
306
+ }
307
+ /**
308
+ * Creates the state of a `starting` level on `connection`: a savepoint of `parent`, or a transaction, in the session `parent` if given.
309
+ *
310
+ * It sends no SQL and does not reserve the level as the `child` of `parent`; the caller does both.
311
+ * `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
312
+ * A transaction of `transaction` that is not a savepoint watches its connection for a `ConnectionError`.
313
+ * `isolation` is the isolation level that a level that is not a savepoint begins with.
314
+ */
315
+ function createLevel(connection, parent, transaction, isolation) {
316
+ const enclosing = parent && !parent.session ? parent : void 0;
317
+ const watch = transaction && !enclosing ? new ConnectionWatch(connection) : void 0;
318
+ return {
319
+ connection: watch ?? connection,
320
+ watch,
321
+ parent,
322
+ depth: enclosing ? enclosing.depth + 1 : 0,
323
+ session: false,
324
+ savepoint: enclosing !== void 0,
325
+ inTransaction: transaction || (enclosing?.inTransaction ?? false),
326
+ state: "starting",
327
+ child: void 0,
328
+ rollbackOnly: void 0,
329
+ cleanup: void 0,
330
+ endedBy: void 0,
331
+ restore: void 0,
332
+ isolation: enclosing ? enclosing.isolation : isolation,
333
+ locks: void 0
334
+ };
335
+ }
336
+ /**
337
+ * Starts a nested level of `parent` with the settings of `options`, at the isolation level of its transaction, rejecting when it cannot start.
338
+ *
339
+ * `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
340
+ * A level whose `isolation` differs from that of its transaction rejects without `SAVEPOINT`, since PostgreSQL sets the isolation level only for a whole transaction.
341
+ * A level whose settings fail rolls back to its savepoint first.
342
+ * Aborted by `signal` while it checks `isolation`, it rejects with the reason without `SAVEPOINT`.
343
+ * The failure suppresses the reason of `signal` when the signal aborted before it.
344
+ */
345
+ async function startNestedLevel(parent, transaction, { signal, settings, isolation } = noLevelOptions) {
346
+ const level = createLevel(parent.connection, parent, transaction);
347
+ parent.child = level;
348
+ if (isolation) {
349
+ level.state = "reserved";
350
+ await checkIsolation(level, isolation, signal);
351
+ if (level.cleanup) return level;
352
+ if (signal?.aborted) {
353
+ closeStarting(level);
354
+ throw signal.reason;
355
+ }
356
+ level.state = "starting";
357
+ }
358
+ try {
359
+ await level.connection.execute(createStatement(`SAVEPOINT ${getSavepointName(level)}`));
360
+ } catch (error) {
361
+ if (!level.cleanup) {
362
+ closeStarting(level);
363
+ throw suppressAbortReason(error, signal);
364
+ }
365
+ throw suppressAbortReason(combineRejections({ error }, await level.cleanup).error, signal);
366
+ }
367
+ if (settings) try {
368
+ await applySettings(level, settings);
369
+ } catch (error) {
370
+ throw suppressAbortReason(combineRejections({ error }, await endLevel(level, false)).error, signal);
371
+ }
372
+ return level;
373
+ }
374
+ /**
375
+ * Checks that the transaction of a `reserved` nested `level` runs at `isolation`, and rejects otherwise, closing the level.
376
+ *
377
+ * A failure suppresses the reason of `signal` when the signal aborted before it.
378
+ * A level that its enclosing level ended during the check returns with its cleanup started.
379
+ */
380
+ async function checkIsolation(level, isolation, signal) {
381
+ let actual = level.isolation;
382
+ if (!actual) {
383
+ try {
384
+ actual = (await level.connection.execute(readIsolationStatement)).rows[0][0];
385
+ } catch (error) {
386
+ if (!level.cleanup) closeStarting(level);
387
+ throw suppressAbortReason(error, signal);
388
+ }
389
+ level.parent.isolation = actual;
390
+ level.isolation = actual;
391
+ if (level.cleanup) return;
392
+ }
393
+ if (actual !== isolation) {
394
+ closeStarting(level);
395
+ throw suppressAbortReason(/* @__PURE__ */ new Error(`Cannot start a nested transaction at isolation level ${isolation}: its transaction runs at ${actual}, and PostgreSQL sets the isolation level of a whole transaction`), signal);
396
+ }
397
+ }
398
+ /**
399
+ * Runs `callback` in the context of a started `level` in `storage`, then ends the level.
400
+ *
401
+ * A level that `commits` commits only when the callback resolves with no nested level running and no nested level that failed to end.
402
+ * When `signal` aborts, the level ends at once without waiting for the callback, and its outcome is a rejection with the reason of the signal.
403
+ */
404
+ async function runLevel(storage, level, callback, commits, signal) {
405
+ if (level.cleanup) return getEndedOutcome(level.cleanup, createClosedError(level, void 0));
406
+ let result;
407
+ let rejection;
408
+ if (signal?.aborted) endLevel(level, false, "abort");
409
+ else {
410
+ level.state = "open";
411
+ try {
412
+ result = await (signal ? settleUnlessAborted(() => storage.run(level, callback), signal, () => {
413
+ endLevel(level, false, "abort");
414
+ return true;
415
+ }) : storage.run(level, callback));
416
+ } catch (error) {
417
+ rejection = { error };
418
+ }
419
+ }
420
+ if (level.cleanup) return getEndedOutcome(level.cleanup, level.endedBy === "abort" ? signal?.reason : createClosedError(level, rejection));
421
+ if (!rejection && level.child) rejection = { error: /* @__PURE__ */ new Error(level.session ? "Session ended with an error: a transaction of it was still running when its callback finished" : "Transaction was rolled back: a nested transaction was still running when its callback finished") };
422
+ if (!rejection && level.rollbackOnly) rejection = { error: new Error("Transaction was rolled back: a nested transaction failed to end", { cause: level.rollbackOnly.error }) };
423
+ const cleanup = await endLevel(level, commits && !rejection);
424
+ return {
425
+ result,
426
+ rejection: combineRejections(rejection, cleanup)
427
+ };
428
+ }
429
+ /**
430
+ * Ends `level` once, whoever ends it: later calls return the cleanup already started.
431
+ *
432
+ * Statements of the level and of all levels nested in it fail from the start of the cleanup, and the nested levels end before it.
433
+ */
434
+ function endLevel(level, commit, endedBy) {
435
+ if (level.cleanup) return level.cleanup;
436
+ level.endedBy = endedBy;
437
+ if (level.state === "reserved") {
438
+ closeStarting(level);
439
+ level.cleanup = Promise.resolve({});
440
+ return level.cleanup;
441
+ }
442
+ level.state = "closing";
443
+ level.cleanup = cleanUpLevel(level, commit, level.child && endLevel(level.child, false, "enclosing level"));
444
+ return level.cleanup;
445
+ }
446
+ async function cleanUpLevel(level, commit, nested) {
447
+ const nestedCleanup = await nested;
448
+ const { parent } = level;
449
+ let cleanup;
450
+ if (level.session) cleanup = {};
451
+ else if (level.savepoint) {
452
+ cleanup = await endSavepoint(level, commit && !level.rollbackOnly);
453
+ parent.rollbackOnly ??= cleanup.rejection;
454
+ } else cleanup = await endTransaction(level, commit);
455
+ level.state = "closed";
456
+ if (parent?.child === level) parent.child = void 0;
457
+ return { rejection: combineRejections(nestedCleanup?.rejection, cleanup) };
458
+ }
459
+ /**
460
+ * Ends a transaction with `COMMIT` or `ROLLBACK`.
461
+ *
462
+ * A transaction whose watch recorded a `ConnectionError` rolls back instead of `COMMIT` and rejects with that error, as if its callback had rejected with it.
463
+ */
464
+ async function endTransaction(level, commit) {
465
+ const { connection, watch } = level;
466
+ const lost = commit && watch?.failure ? { error: watch.failure.error } : void 0;
467
+ try {
468
+ if (commit && !lost) {
469
+ if (watch) watch.phase = "commit";
470
+ const { command } = await connection.execute(commitStatement);
471
+ if (command !== "COMMIT") return { rejection: { error: /* @__PURE__ */ new Error("Transaction was rolled back: a statement in it failed") } };
472
+ return {};
473
+ }
474
+ await connection.execute(rollbackStatement);
475
+ return lost ? { rejection: lost } : {};
476
+ } catch (error) {
477
+ return { rejection: suppress(error, lost, "Transaction rollback failed") };
478
+ }
479
+ }
480
+ /**
481
+ * Ends the savepoint of a nested level, releasing it on commit and rolling back to it otherwise.
482
+ *
483
+ * Before a release, it restores the settings that the level changed; a rollback restores them itself.
484
+ */
485
+ async function endSavepoint(level, commit) {
486
+ const name = getSavepointName(level);
487
+ try {
488
+ if (!commit) await level.connection.execute(createStatement(`ROLLBACK TO SAVEPOINT ${name}`));
489
+ else if (level.restore) await level.connection.execute(level.restore);
490
+ await level.connection.execute(createStatement(`RELEASE SAVEPOINT ${name}`));
491
+ return {};
492
+ } catch (error) {
493
+ return { rejection: { error } };
494
+ }
495
+ }
496
+ /** Name of the savepoint of a nested level; a level has at most one nested level, so the depth makes it unique. */
497
+ function getSavepointName(level) {
498
+ return `kern_${level.depth}`;
499
+ }
500
+ /** Closes a level whose `SAVEPOINT` or `BEGIN` was not sent or failed, freeing its parent for other statements. */
501
+ function closeStarting(level) {
502
+ level.state = "closed";
503
+ if (level.parent?.child === level) level.parent.child = void 0;
504
+ }
505
+ /**
506
+ * Creates the error of `operation` on a level that rejects statements and nested levels, or returns `undefined` when it accepts them.
507
+ *
508
+ * A level that can only roll back keeps the failure of its nested level as the cause.
509
+ */
510
+ function createUnavailableError(level, operation) {
511
+ if (level.session) return createSessionUnavailableError(level, operation);
512
+ if (level.state !== "open") return /* @__PURE__ */ new Error(`Cannot ${operation}: its transaction is already finished`);
513
+ if (level.child) return /* @__PURE__ */ new Error(`Cannot ${operation}: a nested transaction is running; await it first`);
514
+ if (level.rollbackOnly) return new Error(`Cannot ${operation}: a nested transaction failed to end, so the transaction can only roll back`, { cause: level.rollbackOnly.error });
515
+ }
516
+ /** Creates the error of `operation` on a session that rejects statements and transactions, or returns `undefined` when it accepts them. */
517
+ function createSessionUnavailableError(level, operation) {
518
+ if (level.state !== "open") return /* @__PURE__ */ new Error(`Cannot ${operation}: its session is already finished`);
519
+ if (level.child) return /* @__PURE__ */ new Error(`Cannot ${operation}: a transaction of the session is running; await it first`);
520
+ const failure = level.watch?.failure;
521
+ if (failure) return new Error(`Cannot ${operation}: the connection of the session failed, so the session accepts no more statements`, { cause: failure.error });
522
+ }
523
+ /** Error of a level that another level ended, keeping the rejection of its callback as the cause. */
524
+ function createClosedError(level, rejection) {
525
+ return new Error(level.endedBy === "preemption" ? "Transaction was rolled back: its test fixture started another level before it finished" : level.parent?.session ? "Transaction was rolled back: its session ended first" : "Transaction was rolled back: its enclosing transaction ended first", rejection && { cause: rejection.error });
526
+ }
527
+ /** Outcome of a level that something other than its callback ended: a rejection with `error`, which a failed cleanup suppresses. */
528
+ async function getEndedOutcome(cleanup, error) {
529
+ return { rejection: combineRejections({ error }, await cleanup) };
530
+ }
531
+ /** Error of a failure to start a test transaction, which suppresses the reason of `signal` when the signal aborted before the failure. */
532
+ function suppressAbortReason(error, signal) {
533
+ return signal?.aborted ? suppress(error, { error: signal.reason }, "Starting the test transaction failed").error : error;
534
+ }
535
+ /** Rejection with the error of a cleanup, which suppresses the `earlier` rejection if there is one. */
536
+ function combineRejections(earlier, cleanup) {
537
+ return cleanup.rejection ? suppress(cleanup.rejection.error, earlier, "Transaction rollback failed") : earlier;
538
+ }
539
+ /** Rejection with `error`, which suppresses the `earlier` rejection if there is one. */
540
+ function suppress(error, earlier, message) {
541
+ return { error: earlier ? new SuppressedError(error, earlier.error, message) : error };
542
+ }
543
+ // src/transaction/fixture-stack.ts
544
+ /** Open test fixtures of a transaction coordinator and the queue of the changes of their stack. */
545
+ var TestFixtureStack = class {
546
+ #driver;
547
+ #onReleaseError;
548
+ /** Async context of the levels of the coordinator, which tells whether a call runs inside a level. */
549
+ #storage;
550
+ /** Open test fixtures, the innermost last. */
551
+ #fixtures = [];
552
+ /** Changes of the fixture stack, run one after another so that each one sees the stack the previous one left. */
553
+ #transitions = Promise.resolve();
554
+ /**
555
+ * The `close` functions of the test fixtures that are still opening, from the call that starts the opening until the fixture enters the stack or the opening ends otherwise.
556
+ *
557
+ * An opening holds the queue of the fixture stack while its seed runs, so a closing of the stack queued behind it would wait for a seed that may never settle.
558
+ * Closing the stack therefore calls these first: each one tells its opening to stop waiting for the seed.
559
+ */
560
+ #openings = /* @__PURE__ */ new Set();
561
+ /**
562
+ * Outermost fixture in the stack whose closing is queued but has not finished.
563
+ *
564
+ * Openings that start after that closing is requested nest outside of the fixture once it closes, so a repeated closing of it, or a closing of a fixture nested in it, must not close them.
565
+ */
566
+ #closing;
567
+ constructor(driver, storage, onReleaseError) {
568
+ this.#driver = driver;
569
+ this.#storage = storage;
570
+ this.#onReleaseError = onReleaseError;
571
+ }
572
+ /**
573
+ * Starts opening a test fixture with `options`, see `startTestFixture` of the coordinator, or, given `refusal`, returns a fixture that rejects with it.
574
+ *
575
+ * The `close` of a refused fixture resolves once the earlier changes of the stack end.
576
+ */
577
+ start(seed, refusal, options) {
578
+ let level;
579
+ let closeRequested = false;
580
+ const closing = Promise.withResolvers();
581
+ let closedRejection;
582
+ const close = () => {
583
+ if (this.#storage.getStore()) return Promise.reject(/* @__PURE__ */ new Error("Cannot close a test fixture inside a transaction"));
584
+ closeRequested = true;
585
+ closing.resolve();
586
+ if (level && this.#fixtures.includes(level)) {
587
+ const opened = level;
588
+ if (this.#closing && this.#closing.depth <= opened.depth) return this.#enqueue(() => this.#closeFixture(opened));
589
+ this.#closing = opened;
590
+ return this.#closeAfterOpenings(() => this.#enqueue(async () => {
591
+ try {
592
+ await this.#closeFixture(opened);
593
+ } finally {
594
+ if (this.#closing === opened) this.#closing = void 0;
595
+ }
596
+ }));
597
+ }
598
+ return this.#enqueue(() => {
599
+ const rejection = closedRejection;
600
+ closedRejection = void 0;
601
+ if (rejection) throw rejection.error;
602
+ });
603
+ };
604
+ if (refusal) return {
605
+ fixture: Promise.reject(refusal),
606
+ close
607
+ };
608
+ /**
609
+ * Runs stages 2 and 3 of the fixture and puts it in the stack, returning `undefined` when closing was requested and the fixture rolled back cleanly.
610
+ *
611
+ * It rejects with a failure to start the fixture, with the rejection of `seed`, or with a failure to roll back, which suppresses the rejection of `seed`.
612
+ */
613
+ const open = async () => {
614
+ if (closeRequested) return;
615
+ const parent = this.#fixtures.at(-1);
616
+ const started = parent ? await this.#startInFixture(parent, options) : await this.#beginFixture(options?.isolation);
617
+ started.state = "open";
618
+ const outcome = closeRequested ? void 0 : await Promise.race([this.#seedFixture(started, seed), closing.promise]);
619
+ if (outcome && !outcome.rejection && !closeRequested) {
620
+ this.#fixtures.push(started);
621
+ level = started;
622
+ this.#openings.delete(close);
623
+ const { result } = outcome;
624
+ return {
625
+ get data() {
626
+ if (started.state !== "open") throw new Error("Test fixture is closed");
627
+ return result;
628
+ },
629
+ close,
630
+ [Symbol.asyncDispose]: close
631
+ };
632
+ }
633
+ const rejection = combineRejections(outcome?.rejection, { rejection: await this.#endFixture(started) });
634
+ if (rejection) throw rejection.error;
635
+ };
636
+ this.#openings.add(close);
637
+ return {
638
+ fixture: this.#enqueue(async () => {
639
+ let opened;
640
+ try {
641
+ opened = await open();
642
+ } catch (error) {
643
+ if (!closeRequested) throw error;
644
+ closedRejection = { error };
645
+ } finally {
646
+ this.#openings.delete(close);
647
+ }
648
+ if (!opened) throw new Error("Test fixture was closed before it opened");
649
+ return opened;
650
+ }),
651
+ close
652
+ };
653
+ }
654
+ /**
655
+ * Starts the level of a test transaction in the innermost test fixture as a change of the fixture stack, resolving to `undefined` when no fixture is open.
656
+ *
657
+ * Aborted while it waits for its turn, it rejects at once and its change starts nothing, so that a test that has already ended never preempts the active level of the fixture.
658
+ * From its turn on, it settles with its change.
659
+ */
660
+ startTestLevel(options) {
661
+ const signal = options?.signal;
662
+ let waiting = true;
663
+ const start = () => this.#enqueue(() => {
664
+ if (signal?.aborted) return;
665
+ waiting = false;
666
+ const fixture = this.#fixtures.at(-1);
667
+ return fixture && this.#startInFixture(fixture, options);
668
+ });
669
+ return signal ? settleUnlessAborted(start, signal, () => waiting) : start();
670
+ }
671
+ /**
672
+ * Closes every fixture, including those still opening, rejecting with the failures of all of them.
673
+ *
674
+ * Unlike closing a fixture, it closes every opening, including one started after a closing of the stack was requested, since nothing may stay open.
675
+ */
676
+ close() {
677
+ return this.#closeAfterOpenings(() => this.#enqueue(() => {
678
+ const root = this.#fixtures[0];
679
+ return root && this.#closeFixture(root);
680
+ }));
681
+ }
682
+ /**
683
+ * Closes the fixtures still opening and runs `closeStack`, rejecting with the failures of all of them.
684
+ *
685
+ * A seed that never settles holds the queue of the fixture stack, so closing is requested for every opening before `closeStack` queues its change, and the rollback does not wait for the seeds.
686
+ */
687
+ async #closeAfterOpenings(closeStack) {
688
+ const closings = [];
689
+ for (const closeOpening of this.#openings) closings.push(closeOpening());
690
+ closings.push(closeStack());
691
+ let rejection;
692
+ for (const closing of await Promise.allSettled(closings)) if (closing.status === "rejected") rejection = suppress(closing.reason, rejection, "Closing a test fixture failed");
693
+ if (rejection) throw rejection.error;
694
+ }
695
+ /**
696
+ * Runs `seed` in a savepoint of a fixture that is released when it resolves, returning its result or rejection.
697
+ *
698
+ * The savepoint has its own context, so late code of `seed` cannot reach the fixture after it ends.
699
+ */
700
+ async #seedFixture(fixture, seed) {
701
+ try {
702
+ return await runLevel(this.#storage, await startNestedLevel(fixture, false), seed, true);
703
+ } catch (error) {
704
+ return { rejection: { error } };
705
+ }
706
+ }
707
+ /** Closes an opened fixture as a change of the fixture stack. */
708
+ async #closeFixture(fixture) {
709
+ const index = this.#fixtures.indexOf(fixture);
710
+ if (index < 0) return;
711
+ let rejection;
712
+ try {
713
+ rejection = await this.#endFixture(fixture);
714
+ } finally {
715
+ this.#fixtures.length = index;
716
+ }
717
+ if (rejection) throw rejection.error;
718
+ }
719
+ /** Rolls back a fixture and releases the connection of the first one, returning the error to reject with. */
720
+ async #endFixture(fixture) {
721
+ const { rejection } = await endLevel(fixture, false);
722
+ if (!fixture.parent) await releaseConnection(fixture.connection, this.#onReleaseError);
723
+ return rejection;
724
+ }
725
+ /** Runs a change of the fixture stack after the earlier ones. */
726
+ #enqueue(transition) {
727
+ const result = this.#transitions.then(transition);
728
+ this.#transitions = result.catch(() => {});
729
+ return result;
730
+ }
731
+ /** Starts the first test fixture: a transaction at `isolation` on a reserved connection. */
732
+ async #beginFixture(isolation) {
733
+ const connection = await this.#driver.acquire();
734
+ try {
735
+ await connection.execute(getBeginStatement(isolation));
736
+ } catch (error) {
737
+ await releaseConnection(connection, this.#onReleaseError);
738
+ throw error;
739
+ }
740
+ return createLevel(connection, void 0, false, isolation);
741
+ }
742
+ /**
743
+ * Starts a nested level of a test fixture with `options`, preempting its active one.
744
+ *
745
+ * A fixture runs one nested level at a time, so a new one ends the previous one, such as a test that timed out.
746
+ * A level whose `signal` aborts while it preempts rejects with its reason instead of starting, and a failure to start after the abort suppresses the reason.
747
+ */
748
+ async #startInFixture(fixture, options) {
749
+ const signal = options?.signal;
750
+ if (fixture.child) {
751
+ const cleanup = await endLevel(fixture.child, false, "preemption");
752
+ if (cleanup.rejection) throw suppressAbortReason(new Error("Cannot start a level of the test fixture: ending its active level failed", { cause: cleanup.rejection.error }), signal);
753
+ }
754
+ const unavailable = createUnavailableError(fixture, "start a level of the test fixture");
755
+ if (unavailable) throw suppressAbortReason(unavailable, signal);
756
+ signal?.throwIfAborted();
757
+ return startNestedLevel(fixture, false, options);
758
+ }
759
+ };
760
+ // src/error-condition-codes.ts
761
+ /**
762
+ * Condition names of the SQLSTATE codes of PostgreSQL errors, by code: the codes of type `E` with a condition name, which PL/pgSQL takes in `EXCEPTION WHEN`, under the headings of their classes.
763
+ *
764
+ * A code that ends in `000` is the condition of its class.
765
+ * A few names have two codes, such as `null_value_not_allowed`.
766
+ */
767
+ const conditionsByCode = {
768
+ "03000": "sql_statement_not_yet_complete",
769
+ "08000": "connection_exception",
770
+ "08003": "connection_does_not_exist",
771
+ "08006": "connection_failure",
772
+ "08001": "sqlclient_unable_to_establish_sqlconnection",
773
+ "08004": "sqlserver_rejected_establishment_of_sqlconnection",
774
+ "08007": "transaction_resolution_unknown",
775
+ "08P01": "protocol_violation",
776
+ "09000": "triggered_action_exception",
777
+ "0A000": "feature_not_supported",
778
+ "0B000": "invalid_transaction_initiation",
779
+ "0F000": "locator_exception",
780
+ "0F001": "invalid_locator_specification",
781
+ "0L000": "invalid_grantor",
782
+ "0LP01": "invalid_grant_operation",
783
+ "0P000": "invalid_role_specification",
784
+ "0Z000": "diagnostics_exception",
785
+ "0Z002": "stacked_diagnostics_accessed_without_active_handler",
786
+ "10608": "invalid_argument_for_xquery",
787
+ "20000": "case_not_found",
788
+ "21000": "cardinality_violation",
789
+ "22000": "data_exception",
790
+ "2202E": "array_subscript_error",
791
+ "22021": "character_not_in_repertoire",
792
+ "22008": "datetime_field_overflow",
793
+ "22012": "division_by_zero",
794
+ "22005": "error_in_assignment",
795
+ "2200B": "escape_character_conflict",
796
+ "22022": "indicator_overflow",
797
+ "22015": "interval_field_overflow",
798
+ "2201E": "invalid_argument_for_logarithm",
799
+ "22014": "invalid_argument_for_ntile_function",
800
+ "22016": "invalid_argument_for_nth_value_function",
801
+ "2201F": "invalid_argument_for_power_function",
802
+ "2201G": "invalid_argument_for_width_bucket_function",
803
+ "22018": "invalid_character_value_for_cast",
804
+ "22007": "invalid_datetime_format",
805
+ "22019": "invalid_escape_character",
806
+ "2200D": "invalid_escape_octet",
807
+ "22025": "invalid_escape_sequence",
808
+ "22P06": "nonstandard_use_of_escape_character",
809
+ "22010": "invalid_indicator_parameter_value",
810
+ "22023": "invalid_parameter_value",
811
+ "22013": "invalid_preceding_or_following_size",
812
+ "2201B": "invalid_regular_expression",
813
+ "2201W": "invalid_row_count_in_limit_clause",
814
+ "2201X": "invalid_row_count_in_result_offset_clause",
815
+ "2202H": "invalid_tablesample_argument",
816
+ "2202G": "invalid_tablesample_repeat",
817
+ "22009": "invalid_time_zone_displacement_value",
818
+ "2200C": "invalid_use_of_escape_character",
819
+ "2200G": "most_specific_type_mismatch",
820
+ "22004": "null_value_not_allowed",
821
+ "22002": "null_value_no_indicator_parameter",
822
+ "22003": "numeric_value_out_of_range",
823
+ "2200H": "sequence_generator_limit_exceeded",
824
+ "22026": "string_data_length_mismatch",
825
+ "22001": "string_data_right_truncation",
826
+ "22011": "substring_error",
827
+ "22027": "trim_error",
828
+ "22024": "unterminated_c_string",
829
+ "2200F": "zero_length_character_string",
830
+ "22P01": "floating_point_exception",
831
+ "22P02": "invalid_text_representation",
832
+ "22P03": "invalid_binary_representation",
833
+ "22P04": "bad_copy_file_format",
834
+ "22P05": "untranslatable_character",
835
+ "2200L": "not_an_xml_document",
836
+ "2200M": "invalid_xml_document",
837
+ "2200N": "invalid_xml_content",
838
+ "2200S": "invalid_xml_comment",
839
+ "2200T": "invalid_xml_processing_instruction",
840
+ "22030": "duplicate_json_object_key_value",
841
+ "22031": "invalid_argument_for_sql_json_datetime_function",
842
+ "22032": "invalid_json_text",
843
+ "22033": "invalid_sql_json_subscript",
844
+ "22034": "more_than_one_sql_json_item",
845
+ "22035": "no_sql_json_item",
846
+ "22036": "non_numeric_sql_json_item",
847
+ "22037": "non_unique_keys_in_a_json_object",
848
+ "22038": "singleton_sql_json_item_required",
849
+ "22039": "sql_json_array_not_found",
850
+ "2203A": "sql_json_member_not_found",
851
+ "2203B": "sql_json_number_not_found",
852
+ "2203C": "sql_json_object_not_found",
853
+ "2203D": "too_many_json_array_elements",
854
+ "2203E": "too_many_json_object_members",
855
+ "2203F": "sql_json_scalar_required",
856
+ "2203G": "sql_json_item_cannot_be_cast_to_target_type",
857
+ "23000": "integrity_constraint_violation",
858
+ "23001": "restrict_violation",
859
+ "23502": "not_null_violation",
860
+ "23503": "foreign_key_violation",
861
+ "23505": "unique_violation",
862
+ "23514": "check_violation",
863
+ "23P01": "exclusion_violation",
864
+ "24000": "invalid_cursor_state",
865
+ "25000": "invalid_transaction_state",
866
+ "25001": "active_sql_transaction",
867
+ "25002": "branch_transaction_already_active",
868
+ "25008": "held_cursor_requires_same_isolation_level",
869
+ "25003": "inappropriate_access_mode_for_branch_transaction",
870
+ "25004": "inappropriate_isolation_level_for_branch_transaction",
871
+ "25005": "no_active_sql_transaction_for_branch_transaction",
872
+ "25006": "read_only_sql_transaction",
873
+ "25007": "schema_and_data_statement_mixing_not_supported",
874
+ "25P01": "no_active_sql_transaction",
875
+ "25P02": "in_failed_sql_transaction",
876
+ "25P03": "idle_in_transaction_session_timeout",
877
+ "25P04": "transaction_timeout",
878
+ "26000": "invalid_sql_statement_name",
879
+ "27000": "triggered_data_change_violation",
880
+ "28000": "invalid_authorization_specification",
881
+ "28P01": "invalid_password",
882
+ "2B000": "dependent_privilege_descriptors_still_exist",
883
+ "2BP01": "dependent_objects_still_exist",
884
+ "2D000": "invalid_transaction_termination",
885
+ "2F000": "sql_routine_exception",
886
+ "2F005": "function_executed_no_return_statement",
887
+ "2F002": "modifying_sql_data_not_permitted",
888
+ "2F003": "prohibited_sql_statement_attempted",
889
+ "2F004": "reading_sql_data_not_permitted",
890
+ "34000": "invalid_cursor_name",
891
+ "38000": "external_routine_exception",
892
+ "38001": "containing_sql_not_permitted",
893
+ "38002": "modifying_sql_data_not_permitted",
894
+ "38003": "prohibited_sql_statement_attempted",
895
+ "38004": "reading_sql_data_not_permitted",
896
+ "39000": "external_routine_invocation_exception",
897
+ "39001": "invalid_sqlstate_returned",
898
+ "39004": "null_value_not_allowed",
899
+ "39P01": "trigger_protocol_violated",
900
+ "39P02": "srf_protocol_violated",
901
+ "39P03": "event_trigger_protocol_violated",
902
+ "3B000": "savepoint_exception",
903
+ "3B001": "invalid_savepoint_specification",
904
+ "3D000": "invalid_catalog_name",
905
+ "3F000": "invalid_schema_name",
906
+ "40000": "transaction_rollback",
907
+ "40002": "transaction_integrity_constraint_violation",
908
+ "40001": "serialization_failure",
909
+ "40003": "statement_completion_unknown",
910
+ "40P01": "deadlock_detected",
911
+ "42000": "syntax_error_or_access_rule_violation",
912
+ "42601": "syntax_error",
913
+ "42501": "insufficient_privilege",
914
+ "42846": "cannot_coerce",
915
+ "42803": "grouping_error",
916
+ "42P20": "windowing_error",
917
+ "42P19": "invalid_recursion",
918
+ "42830": "invalid_foreign_key",
919
+ "42602": "invalid_name",
920
+ "42622": "name_too_long",
921
+ "42939": "reserved_name",
922
+ "42804": "datatype_mismatch",
923
+ "42P18": "indeterminate_datatype",
924
+ "42P21": "collation_mismatch",
925
+ "42P22": "indeterminate_collation",
926
+ "42809": "wrong_object_type",
927
+ "428C9": "generated_always",
928
+ "42703": "undefined_column",
929
+ "42883": "undefined_function",
930
+ "42P01": "undefined_table",
931
+ "42P02": "undefined_parameter",
932
+ "42704": "undefined_object",
933
+ "42701": "duplicate_column",
934
+ "42P03": "duplicate_cursor",
935
+ "42P04": "duplicate_database",
936
+ "42723": "duplicate_function",
937
+ "42P05": "duplicate_prepared_statement",
938
+ "42P06": "duplicate_schema",
939
+ "42P07": "duplicate_table",
940
+ "42712": "duplicate_alias",
941
+ "42710": "duplicate_object",
942
+ "42702": "ambiguous_column",
943
+ "42725": "ambiguous_function",
944
+ "42P08": "ambiguous_parameter",
945
+ "42P09": "ambiguous_alias",
946
+ "42P10": "invalid_column_reference",
947
+ "42611": "invalid_column_definition",
948
+ "42P11": "invalid_cursor_definition",
949
+ "42P12": "invalid_database_definition",
950
+ "42P13": "invalid_function_definition",
951
+ "42P14": "invalid_prepared_statement_definition",
952
+ "42P15": "invalid_schema_definition",
953
+ "42P16": "invalid_table_definition",
954
+ "42P17": "invalid_object_definition",
955
+ "44000": "with_check_option_violation",
956
+ "53000": "insufficient_resources",
957
+ "53100": "disk_full",
958
+ "53200": "out_of_memory",
959
+ "53300": "too_many_connections",
960
+ "53400": "configuration_limit_exceeded",
961
+ "54000": "program_limit_exceeded",
962
+ "54001": "statement_too_complex",
963
+ "54011": "too_many_columns",
964
+ "54023": "too_many_arguments",
965
+ "55000": "object_not_in_prerequisite_state",
966
+ "55006": "object_in_use",
967
+ "55P02": "cant_change_runtime_param",
968
+ "55P03": "lock_not_available",
969
+ "55P04": "unsafe_new_enum_value_usage",
970
+ "57000": "operator_intervention",
971
+ "57014": "query_canceled",
972
+ "57P01": "admin_shutdown",
973
+ "57P02": "crash_shutdown",
974
+ "57P03": "cannot_connect_now",
975
+ "57P04": "database_dropped",
976
+ "57P05": "idle_session_timeout",
977
+ "58000": "system_error",
978
+ "58030": "io_error",
979
+ "58P01": "undefined_file",
980
+ "58P02": "duplicate_file",
981
+ "58P03": "file_name_too_long",
982
+ F0000: "config_file_error",
983
+ F0001: "lock_file_exists",
984
+ HV000: "fdw_error",
985
+ HV005: "fdw_column_name_not_found",
986
+ HV002: "fdw_dynamic_parameter_value_needed",
987
+ HV010: "fdw_function_sequence_error",
988
+ HV021: "fdw_inconsistent_descriptor_information",
989
+ HV024: "fdw_invalid_attribute_value",
990
+ HV007: "fdw_invalid_column_name",
991
+ HV008: "fdw_invalid_column_number",
992
+ HV004: "fdw_invalid_data_type",
993
+ HV006: "fdw_invalid_data_type_descriptors",
994
+ HV091: "fdw_invalid_descriptor_field_identifier",
995
+ HV00B: "fdw_invalid_handle",
996
+ HV00C: "fdw_invalid_option_index",
997
+ HV00D: "fdw_invalid_option_name",
998
+ HV090: "fdw_invalid_string_length_or_buffer_length",
999
+ HV00A: "fdw_invalid_string_format",
1000
+ HV009: "fdw_invalid_use_of_null_pointer",
1001
+ HV014: "fdw_too_many_handles",
1002
+ HV001: "fdw_out_of_memory",
1003
+ HV00P: "fdw_no_schemas",
1004
+ HV00J: "fdw_option_name_not_found",
1005
+ HV00K: "fdw_reply_handle",
1006
+ HV00Q: "fdw_schema_not_found",
1007
+ HV00R: "fdw_table_not_found",
1008
+ HV00L: "fdw_unable_to_create_execution",
1009
+ HV00M: "fdw_unable_to_create_reply",
1010
+ HV00N: "fdw_unable_to_establish_connection",
1011
+ P0000: "plpgsql_error",
1012
+ P0001: "raise_exception",
1013
+ P0002: "no_data_found",
1014
+ P0003: "too_many_rows",
1015
+ P0004: "assert_failure",
1016
+ XX000: "internal_error",
1017
+ XX001: "data_corrupted",
1018
+ XX002: "index_corrupted"
1019
+ };
1020
+ // src/error-condition.ts
1021
+ /**
1022
+ * Tells whether `error` is a `QueryError` of `condition`, including a `QueryError` of another copy of kern-db.
1023
+ *
1024
+ * The condition of a class, such as `integrity_constraint_violation`, matches every error of its class, as in PL/pgSQL.
1025
+ */
1026
+ function isQueryError(error, condition) {
1027
+ if (!(error instanceof QueryError)) return false;
1028
+ const conditions = conditionsByCode;
1029
+ return conditions[error.code] === condition || conditions[`${error.code.slice(0, 2)}000`] === condition;
1030
+ }
1031
+ // src/transaction/session.ts
1032
+ const beginStatement = /* @__PURE__ */ createStatement("BEGIN");
1033
+ /**
1034
+ * Creates the state of a `starting` session on `connection`.
1035
+ *
1036
+ * It watches its connection for a `ConnectionError`, after which it accepts no statements: the connection may be lost, and a statement that a client ran on a new connection would run without the settings of the session.
1037
+ */
1038
+ function createSessionLevel(connection) {
1039
+ const watch = new ConnectionWatch(connection);
1040
+ return {
1041
+ connection: watch,
1042
+ watch,
1043
+ parent: void 0,
1044
+ depth: 0,
1045
+ session: true,
1046
+ savepoint: false,
1047
+ inTransaction: false,
1048
+ state: "starting",
1049
+ child: void 0,
1050
+ rollbackOnly: void 0,
1051
+ cleanup: void 0,
1052
+ endedBy: void 0,
1053
+ restore: void 0,
1054
+ isolation: void 0,
1055
+ locks: void 0
1056
+ };
1057
+ }
1058
+ /**
1059
+ * Releases the advisory locks that an ended session took, restores the settings that it changed, and returns its connection to the driver.
1060
+ *
1061
+ * A connection whose locks fail to release or whose settings fail to restore is left in a transaction, so that the driver closes it rather than returning it to the pool with them, and so is one whose statement of `tryLock` failed, which may hold a lock that the session does not know of.
1062
+ * The failure does not change the result or the error of the session, which is decided before.
1063
+ * A failed release of the locks or restore of the settings goes to `onReleaseError`, or to `console.error` without one, unless it is a `ConnectionError`, which a lost connection gives, with nothing to restore.
1064
+ */
1065
+ async function releaseSession(level, onReleaseError) {
1066
+ if (!await restoreSession(level, onReleaseError)) await leaveInTransaction(level.connection);
1067
+ await releaseConnection(level.connection, onReleaseError);
1068
+ }
1069
+ /**
1070
+ * Releases the advisory locks of an ended session and then restores its settings, and tells whether its connection can return to the pool.
1071
+ *
1072
+ * It sends them also after a `ConnectionError`: a client that refused a statement without sending it keeps the connection, which the driver then returns to the pool.
1073
+ */
1074
+ async function restoreSession(level, onReleaseError) {
1075
+ const { connection, locks, restore } = level;
1076
+ if (locks) {
1077
+ const keys = await locks.settle();
1078
+ if (!keys) return false;
1079
+ if (keys.length > 0 && !await executeRestoring(connection, createUnlockStatement(keys), "Releasing the advisory locks of a session failed", onReleaseError)) return false;
1080
+ }
1081
+ return !restore || await executeRestoring(connection, restore, "Restoring the settings of a session failed", onReleaseError);
1082
+ }
1083
+ /** Executes a statement that restores the state of the connection of an ended session, and tells whether it completed, reporting a failure other than a `ConnectionError` with `message`. */
1084
+ async function executeRestoring(connection, statement, message, onReleaseError) {
1085
+ try {
1086
+ await connection.execute(statement);
1087
+ return true;
1088
+ } catch (error) {
1089
+ if (!(error instanceof ConnectionError)) reportReleaseError(new Error(message, { cause: error }), onReleaseError);
1090
+ return false;
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Leaves `connection` in a transaction or failed, so that the driver closes it on release, which the driver contract requires for a connection that is not idle or that failed with a `ConnectionError`.
1095
+ *
1096
+ * It sends `BEGIN` again while PostgreSQL rejects it and may leave the connection idle, such as when a cancel request hits the statement, since such a connection would return to the pool.
1097
+ * A client that refuses `BEGIN` without sending it, with a `ConnectionError`, keeps an idle connection that the driver returns to the pool: kern-db cannot tell that from a lost connection, on which sending `BEGIN` again would never end.
1098
+ */
1099
+ async function leaveInTransaction(connection) {
1100
+ for (;;) try {
1101
+ await connection.execute(beginStatement);
1102
+ return;
1103
+ } catch (error) {
1104
+ if (!(error instanceof QueryError) || isQueryError(error, "in_failed_sql_transaction")) return;
1105
+ }
1106
+ }
1107
+ // src/transaction/coordinator.ts
1108
+ /**
1109
+ * Routes statements and scripts to the ambient transaction or session of the current async context or to the pool.
1110
+ *
1111
+ * This is the only place that decides which connection executes a statement.
1112
+ */
1113
+ var TransactionCoordinator = class {
1114
+ driver;
1115
+ #storage = new AsyncLocalStorage();
1116
+ #fixtures;
1117
+ #onReleaseError;
1118
+ /** Closing of the database, set by the first `close`; from then on, operations outside a transaction reject. */
1119
+ #closed;
1120
+ /** Rejects statements and transactions outside a test transaction or the seed of a test fixture instead of running them on the pool. */
1121
+ test;
1122
+ constructor(driver, { test = false, onReleaseError } = {}) {
1123
+ this.driver = driver;
1124
+ this.test = test;
1125
+ this.#onReleaseError = onReleaseError;
1126
+ this.#fixtures = new TestFixtureStack(driver, this.#storage, onReleaseError);
1127
+ }
1128
+ execute(statement) {
1129
+ const target = this.#route("execute a query");
1130
+ return target instanceof Error ? Promise.reject(target) : target.execute(statement);
1131
+ }
1132
+ /** Executes a script of statements without parameters where `execute` executes a statement. */
1133
+ executeScript(text) {
1134
+ const target = this.#route("execute a script");
1135
+ return target instanceof Error ? Promise.reject(target) : target.executeScript(text);
1136
+ }
1137
+ /** Returns the connection of the ambient transaction or session, or the driver of the pool, that runs `operation`, or the error that rejects it. */
1138
+ #route(operation) {
1139
+ const level = this.#storage.getStore();
1140
+ if (!level) {
1141
+ if (this.#closed) return createDatabaseClosedError(operation);
1142
+ return this.test ? new Error(describeOutsideTest(operation)) : this.driver;
1143
+ }
1144
+ return createUnavailableError(level, operation) ?? level.connection;
1145
+ }
1146
+ /**
1147
+ * Runs `callback` in a transaction that commits when it resolves and rolls back when it rejects, with the settings of `options`.
1148
+ *
1149
+ * Inside a transaction, it runs `callback` in a savepoint nested in it, which restores the settings it changed when it ends, and rejects without the savepoint when the isolation level of `options` differs from that of the transaction.
1150
+ * Inside a session, it starts the transaction on the connection of the session.
1151
+ * A failed cleanup after an error rejects with a `SuppressedError` that keeps the original error in its `suppressed` chain.
1152
+ * A transaction that it starts rolls back instead of `COMMIT` after a `ConnectionError` on its connection, and when it rejects after such an error, also of `COMMIT`, it rejects with `TransactionConnectionError` of the phase of the failure, whose `cause` is the error it would reject with otherwise.
1153
+ */
1154
+ transaction(callback, options) {
1155
+ const levelOptions = options && {
1156
+ settings: getLevelSettings(options),
1157
+ isolation: options.isolation
1158
+ };
1159
+ const parent = this.#storage.getStore();
1160
+ if (parent) return parent.session ? this.#runInSession(parent, callback, levelOptions) : this.#runNested(parent, callback, true, levelOptions);
1161
+ if (this.#closed) return Promise.reject(createDatabaseClosedError("start a transaction"));
1162
+ return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true, levelOptions);
1163
+ }
1164
+ /**
1165
+ * Runs `callback` in the current transaction, or in a new transaction outside of one.
1166
+ *
1167
+ * Joining starts no savepoint, so a rejection of `callback` does not undo its changes.
1168
+ * It rejects without calling `callback` when the current level accepts no statements, as a nested transaction would.
1169
+ */
1170
+ async ensureTransaction(callback) {
1171
+ const level = this.#storage.getStore();
1172
+ if (!level?.inTransaction) return this.transaction(callback);
1173
+ const unavailable = createUnavailableError(level, "join the transaction");
1174
+ if (unavailable) throw unavailable;
1175
+ return callback();
1176
+ }
1177
+ /**
1178
+ * Tells whether the current async context runs in a transaction of `transaction`.
1179
+ *
1180
+ * A test transaction and the seed of a test fixture are not transactions, unless they run inside one, so that code under test behaves as in production.
1181
+ */
1182
+ isInTransaction() {
1183
+ return this.#storage.getStore()?.inTransaction ?? false;
1184
+ }
1185
+ /**
1186
+ * Tells whether a statement of the current async context runs in a transaction block of PostgreSQL: in a transaction, a test transaction, or the seed of a test fixture, but not in a session outside its transactions.
1187
+ *
1188
+ * Unlike `isInTransaction`, it counts the levels of the test helpers, since PostgreSQL rejects a statement such as `CREATE INDEX CONCURRENTLY` in any transaction block.
1189
+ */
1190
+ isInTransactionBlock() {
1191
+ const level = this.#storage.getStore();
1192
+ return level !== void 0 && !level.session;
1193
+ }
1194
+ /**
1195
+ * Runs `callback` in a session: on one reserved connection outside a transaction, with the settings of `options` until it ends.
1196
+ *
1197
+ * Statements of the callback run on the connection, each in a transaction of its own, and `transaction` starts a transaction on it.
1198
+ * It rejects without calling `callback` inside a transaction, a test transaction, or another session, and on a coordinator created for tests, whose statements all run in test transactions.
1199
+ * A rejection of `callback` undoes nothing.
1200
+ * After a `ConnectionError` on its connection, the session accepts no more statements.
1201
+ */
1202
+ session(callback, options) {
1203
+ const parent = this.#storage.getStore();
1204
+ if (parent) return Promise.reject(/* @__PURE__ */ new Error(parent.session ? "Cannot start a session inside a session" : "Cannot start a session inside a transaction, also a test transaction: the statements of a session run outside a transaction"));
1205
+ if (this.#closed) return Promise.reject(createDatabaseClosedError("start a session"));
1206
+ if (this.test) return Promise.reject(/* @__PURE__ */ new Error("Cannot start a session: the database was created with test: true, so its statements run in test transactions, and the statements of a session run outside a transaction"));
1207
+ return this.#runSession(callback, getLevelSettings(options));
1208
+ }
1209
+ /**
1210
+ * Takes the advisory lock of `key` until the transaction of `transaction` ends, waiting while another transaction or session holds it.
1211
+ *
1212
+ * It rejects without a statement outside such a transaction, where the lock would end with its statement, and in a session outside its transactions, where the waiting statement keeps a snapshot that `CREATE INDEX CONCURRENTLY` in the session that holds the lock waits for.
1213
+ */
1214
+ async lock(key) {
1215
+ const level = this.#storage.getStore();
1216
+ if (!level?.inTransaction) throw new Error(level?.session ? "Cannot wait for an advisory lock in a session outside a transaction: the waiting statement would keep a snapshot that CREATE INDEX CONCURRENTLY in the session holding the lock waits for; take it with tryLock, which does not wait" : "Cannot take an advisory lock outside a transaction, where it would end with its statement; a test transaction alone does not count");
1217
+ await this.execute(createLockStatement(key));
1218
+ }
1219
+ /**
1220
+ * Takes the advisory lock of `key` unless another transaction or session holds it, and tells whether it took it: until the transaction of `transaction` ends, or in a session outside its transactions, until the session ends.
1221
+ *
1222
+ * It rejects without a statement outside both, where the lock would end with its statement.
1223
+ * A session takes a key that it holds without a statement, and releases its locks when it ends.
1224
+ */
1225
+ async tryLock(key) {
1226
+ const level = this.#storage.getStore();
1227
+ if (level?.inTransaction) return isTaken(await this.execute(createTryLockStatement(key)));
1228
+ if (!level?.session) throw new Error("Cannot take an advisory lock outside a transaction or a session, where it would end with its statement; a test transaction alone does not count");
1229
+ const unavailable = createUnavailableError(level, "take an advisory lock");
1230
+ if (unavailable) throw unavailable;
1231
+ return (level.locks ??= new SessionLocks()).take(level.connection, key);
1232
+ }
1233
+ /**
1234
+ * Takes the advisory lock of `key` in a session outside its transactions until the session ends, calling `tryLock` again after a pause until it takes the lock.
1235
+ *
1236
+ * It rejects without a statement outside a session, and in a transaction, also of a session or of the test helpers, which would keep a snapshot between the attempts; in a transaction of `transaction`, `lock` waits for the lock instead.
1237
+ * Once `signal` of `options` aborts, it makes no more attempts and rejects with its reason, unless the attempt that is running takes the lock.
1238
+ */
1239
+ async pollLock(key, options) {
1240
+ const level = this.#storage.getStore();
1241
+ if (!level?.session) throw new Error(!level ? "Cannot poll for an advisory lock outside a session, where it would end with its statement" : level.inTransaction ? "Cannot poll for an advisory lock in a transaction, which would keep a snapshot between the attempts; take it with lock, which waits for it" : "Cannot poll for an advisory lock in a test transaction, which would keep a snapshot between the attempts; take it with lock inside transaction or ensureTransaction");
1242
+ await pollLock(() => this.tryLock(key), options?.signal);
1243
+ }
1244
+ /**
1245
+ * Runs `callback` in a transaction that always rolls back, on a coordinator created for tests.
1246
+ *
1247
+ * Inside a transaction, it runs `callback` in a savepoint nested in it that always rolls back.
1248
+ * When `signal` aborts, the level stops accepting statements at once and ends without waiting for `callback`.
1249
+ * Once the level ends, the transaction rejects with the reason of the signal.
1250
+ * A failure to start or end the level after the abort suppresses the reason in a `SuppressedError`.
1251
+ * Aborted while it waits for its turn in the fixture stack or for a connection, it rejects at once; after the abort, it never calls `callback`.
1252
+ */
1253
+ testTransaction(callback, options) {
1254
+ const signal = options?.signal;
1255
+ if (!this.test) return Promise.reject(/* @__PURE__ */ new Error("testTransaction requires a database created with test: true"));
1256
+ if (signal?.aborted) return Promise.reject(signal.reason);
1257
+ const parent = this.#storage.getStore();
1258
+ if (parent) return this.#runNested(parent, callback, false, options);
1259
+ if (this.#closed) return Promise.reject(createDatabaseClosedError("start a test transaction"));
1260
+ return this.#fixtures.startTestLevel(options).then((level) => level ? this.#runStarted(level, callback, false, signal) : this.#runRoot(callback, false, options));
1261
+ }
1262
+ /**
1263
+ * Opens a test fixture: a transaction on a reserved connection, or a savepoint in the innermost open fixture, and runs `seed` in it.
1264
+ *
1265
+ * Test transactions started outside any transaction run in the innermost fixture.
1266
+ * A fixture whose `seed` rejects rolls back and closes.
1267
+ */
1268
+ createTestFixture(seed, options) {
1269
+ return this.startTestFixture(seed, options).fixture;
1270
+ }
1271
+ /**
1272
+ * Starts opening a test fixture, see `createTestFixture`, and returns it with a `close` that can be called before it opens.
1273
+ *
1274
+ * Closing before the fixture opens rejects the opening without waiting for `seed` and rolls the fixture back, so a fixture or test transaction started after `close` never nests in it, even when its `seed` never settles.
1275
+ * Closing an open fixture also closes the fixtures opening in it.
1276
+ */
1277
+ startTestFixture(seed, options) {
1278
+ return this.#fixtures.start(seed, this.#getFixtureRefusal(), options);
1279
+ }
1280
+ /** Returns the error that rejects a test fixture created now, or `undefined` when it can open. */
1281
+ #getFixtureRefusal() {
1282
+ if (!this.test) return /* @__PURE__ */ new Error("createTestFixture requires a database created with test: true");
1283
+ if (this.#storage.getStore()) return /* @__PURE__ */ new Error("Cannot create a test fixture inside a transaction");
1284
+ if (this.#closed) return createDatabaseClosedError("create a test fixture");
1285
+ }
1286
+ /**
1287
+ * Closes the test fixtures, including those still opening, and then the driver, which waits for the running transactions and sessions to end.
1288
+ *
1289
+ * A driver waits for every reserved connection, so closing it under an open fixture would wait forever.
1290
+ * A failed closing of the driver suppresses the failures of closing the fixtures.
1291
+ * From the first call, statements, transactions, sessions, and fixtures started outside a transaction and a session reject, while the levels already running can still end; a repeated call returns the same closing.
1292
+ */
1293
+ close() {
1294
+ const level = this.#storage.getStore();
1295
+ if (level) return Promise.reject(/* @__PURE__ */ new Error(`Cannot close the database inside a ${level.session ? "session" : "transaction"}`));
1296
+ this.#closed ??= this.#close();
1297
+ return this.#closed;
1298
+ }
1299
+ /** Closes the test fixtures and then the driver, see `close`. */
1300
+ async #close() {
1301
+ let rejection;
1302
+ try {
1303
+ await this.#fixtures.close();
1304
+ } catch (error) {
1305
+ rejection = { error };
1306
+ }
1307
+ try {
1308
+ await this.driver.close();
1309
+ } catch (error) {
1310
+ rejection = suppress(error, rejection, "Closing the driver failed");
1311
+ }
1312
+ if (rejection) throw rejection.error;
1313
+ }
1314
+ /**
1315
+ * Runs `callback` in a level without a parent on a connection of its own: a transaction of `transaction` when it `commits`, and a test transaction otherwise.
1316
+ *
1317
+ * A transaction of `transaction` whose connection fails rejects with `TransactionConnectionError`.
1318
+ */
1319
+ async #runRoot(callback, commits, options = noLevelOptions) {
1320
+ const { signal } = options;
1321
+ let connection;
1322
+ try {
1323
+ connection = await (signal ? acquireUnlessAborted(this.driver, signal, this.#onReleaseError) : this.driver.acquire());
1324
+ } catch (error) {
1325
+ throw commits && error instanceof ConnectionError ? new TransactionConnectionError("begin", { cause: error }) : error;
1326
+ }
1327
+ if (signal?.aborted) {
1328
+ await releaseConnection(connection, this.#onReleaseError);
1329
+ throw signal.reason;
1330
+ }
1331
+ const level = createLevel(connection, void 0, commits, options.isolation);
1332
+ const outcome = await this.#runTransaction(level, callback, commits, options);
1333
+ await releaseConnection(connection, this.#onReleaseError);
1334
+ return settleTransaction(level, outcome);
1335
+ }
1336
+ /**
1337
+ * Runs `callback` in a transaction of `transaction` on the connection of `session`, which rejects its own statements until the transaction ends.
1338
+ *
1339
+ * A transaction whose connection fails rejects with `TransactionConnectionError`, as one on a connection of its own does.
1340
+ */
1341
+ async #runInSession(session, callback, options = noLevelOptions) {
1342
+ const unavailable = createUnavailableError(session, "start a transaction");
1343
+ if (unavailable) throw unavailable;
1344
+ const level = createLevel(session.connection, session, true, options.isolation);
1345
+ session.child = level;
1346
+ return settleTransaction(level, await this.#runTransaction(level, callback, true, options));
1347
+ }
1348
+ /**
1349
+ * Begins the transaction of `level`, a level that is not a savepoint, applies the settings of `options`, runs `callback` in it, and ends it.
1350
+ *
1351
+ * A level whose `BEGIN` fails closes, and one whose settings fail rolls back.
1352
+ */
1353
+ async #runTransaction(level, callback, commits, { signal, settings, isolation }) {
1354
+ try {
1355
+ await level.connection.execute(getBeginStatement(isolation));
1356
+ } catch (error) {
1357
+ if (level.cleanup) return getEndedOutcome(level.cleanup, error);
1358
+ closeStarting(level);
1359
+ return { rejection: { error: suppressAbortReason(error, signal) } };
1360
+ }
1361
+ if (settings) try {
1362
+ await applySettings(level, settings);
1363
+ } catch (error) {
1364
+ return getEndedOutcome(endLevel(level, false), error);
1365
+ }
1366
+ if (level.watch) level.watch.phase = "transaction";
1367
+ return runLevel(this.#storage, level, callback, commits, signal);
1368
+ }
1369
+ /**
1370
+ * Runs `callback` in a session with `settings` on a connection of its own, and restores the settings before the connection returns to the driver.
1371
+ *
1372
+ * A session whose settings fail to apply rejects without calling `callback`.
1373
+ */
1374
+ async #runSession(callback, settings) {
1375
+ const level = createSessionLevel(await this.driver.acquire());
1376
+ let outcome;
1377
+ if (settings) try {
1378
+ await applySettings(level, settings);
1379
+ } catch (error) {
1380
+ level.state = "closed";
1381
+ outcome = { rejection: { error } };
1382
+ }
1383
+ outcome ??= await runLevel(this.#storage, level, callback, false);
1384
+ await releaseSession(level, this.#onReleaseError);
1385
+ if (outcome.rejection) throw outcome.rejection.error;
1386
+ return outcome.result;
1387
+ }
1388
+ async #runNested(parent, callback, commits, options) {
1389
+ const unavailable = createUnavailableError(parent, "start a nested transaction");
1390
+ if (unavailable) throw unavailable;
1391
+ return this.#runStarted(await startNestedLevel(parent, commits, options), callback, commits, options?.signal);
1392
+ }
1393
+ /** Runs `callback` in a started level, returning its result or throwing its rejection. */
1394
+ async #runStarted(level, callback, commits, signal) {
1395
+ const { result, rejection } = await runLevel(this.#storage, level, callback, commits, signal);
1396
+ if (rejection) throw rejection.error;
1397
+ return result;
1398
+ }
1399
+ };
1400
+ /**
1401
+ * Returns the result of the `outcome` of a transaction of `level`, a level that is not a savepoint, or throws its rejection.
1402
+ *
1403
+ * A failure of the connection decides whether the transaction may have been committed, whatever error the rejection has, so the transaction then rejects with `TransactionConnectionError` of its phase.
1404
+ */
1405
+ function settleTransaction(level, outcome) {
1406
+ const { rejection } = outcome;
1407
+ if (rejection) {
1408
+ const failure = level.watch?.failure;
1409
+ throw failure ? new TransactionConnectionError(failure.phase, { cause: rejection.error }) : rejection.error;
1410
+ }
1411
+ return outcome.result;
1412
+ }
1413
+ /** Error message of an operation outside a test transaction on a coordinator created for tests. */
1414
+ function describeOutsideTest(operation) {
1415
+ return `Cannot ${operation} outside a test transaction: the database was created with test: true, so wrap the test in testTransaction`;
1416
+ }
1417
+ /** Error of `operation` started outside a transaction after closing the database was requested. */
1418
+ function createDatabaseClosedError(operation) {
1419
+ return /* @__PURE__ */ new Error(`Cannot ${operation}: the database is closed`);
1420
+ }
1421
+ // src/database.ts
1422
+ /** Marks a database of `createDatabase` of any copy of kern-db, which `isDatabase` of another copy recognizes. */
1423
+ const databaseBrand = /* @__PURE__ */ Symbol.for("kern-db.Database");
1424
+ /**
1425
+ * Checks whether `value` is a database of `createDatabase`, also of another copy of kern-db, such as to reject a client of PostgreSQL or a `Db` of kern-orm passed instead of a database.
1426
+ *
1427
+ * It checks the brand that `createDatabase` sets, so it rejects a spread copy of a database.
1428
+ */
1429
+ function isDatabase(value) {
1430
+ return typeof value === "object" && value !== null && value[databaseBrand] === true;
1431
+ }
1432
+ const coordinators = /* @__PURE__ */ new WeakMap();
1433
+ /**
1434
+ * Returns the transaction coordinator of a database of `createDatabase`, such as for the test helpers.
1435
+ *
1436
+ * It throws for another object, such as a copy of a database or a database of another copy of this module, which has no coordinator here.
1437
+ */
1438
+ function getCoordinator(database) {
1439
+ const coordinator = coordinators.get(database);
1440
+ if (coordinator === void 0) throw new TypeError("The database was not created by createDatabase of this copy of the package");
1441
+ return coordinator;
1442
+ }
1443
+ /** Creates a database over a driver. */
1444
+ function createDatabase({ driver, ...options }) {
1445
+ const coordinator = new TransactionCoordinator(driver, options);
1446
+ const database = {
1447
+ async script(script, ...values) {
1448
+ return coordinator.executeScript(typeof script === "string" ? script : compileScript(sql(script, ...values)));
1449
+ },
1450
+ query(strings, ...values) {
1451
+ const fragment = sql(strings, ...values);
1452
+ return startSqlQuery(() => compileFragment(fragment), database);
1453
+ },
1454
+ execute: (statement) => coordinator.execute(statement),
1455
+ transaction: (callback, options) => coordinator.transaction(callback, options),
1456
+ ensureTransaction: (callback) => coordinator.ensureTransaction(callback),
1457
+ isInTransaction: () => coordinator.isInTransaction(),
1458
+ isInTransactionBlock: () => coordinator.isInTransactionBlock(),
1459
+ session: (callback, options) => coordinator.session(callback, options),
1460
+ lock: (key) => coordinator.lock(key),
1461
+ tryLock: (key) => coordinator.tryLock(key),
1462
+ pollLock: (key, options) => coordinator.pollLock(key, options),
1463
+ close: () => coordinator.close(),
1464
+ [Symbol.asyncDispose]: () => coordinator.close()
1465
+ };
1466
+ Object.defineProperty(database, databaseBrand, { value: true });
1467
+ coordinators.set(database, coordinator);
1468
+ return database;
1469
+ }
1470
+ export { TransactionConnectionError as a, isQueryError as i, getCoordinator as n, isDatabase as r, createDatabase as t };