@spfn/core 0.3.0-beta.27 → 0.3.0-beta.28

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.
@@ -1,4 +1,4 @@
1
- import { E as EventRouterDef } from '../router-D5B0cpVN.js';
1
+ import { E as EventRouterDef } from '../router-CX5uVjmU.js';
2
2
  import { R as Router } from '../router-4YoIy5fN.js';
3
3
  import { C as ContractDocument, a as ContractViolation, b as ContractOperation, c as ContractSnapshot } from '../types-5Rl11_Nb.js';
4
4
  export { d as CompatibilityPolicy, e as ContractEvent, f as ContractEvents, g as ContractRequest, h as ContractViolationKind, J as JsonSchema } from '../types-5Rl11_Nb.js';
@@ -1165,11 +1165,11 @@ type TransactionContext<TDatabase extends DrizzleDatabase = DrizzleDatabase> = {
1165
1165
  /** Unique transaction ID for logging and tracing */
1166
1166
  txId: string;
1167
1167
  level: number;
1168
- /** Callbacks to execute before the root transaction commits (still inside it) */
1168
+ /** beforeCommit callbacks registered in THIS frame (root or savepoint) */
1169
1169
  beforeCommitCallbacks: BeforeCommitCallback[];
1170
- /** Callbacks to execute after root transaction commits */
1170
+ /** afterCommit callbacks registered in THIS frame (root or savepoint) */
1171
1171
  afterCommitCallbacks: AfterCommitCallback[];
1172
- /** Callbacks to execute after the root transaction rolled back */
1172
+ /** afterRollback callbacks registered in THIS frame (root or savepoint) */
1173
1173
  afterRollbackCallbacks: AfterRollbackCallback[];
1174
1174
  /** Serializes the savepoint frames opened directly off THIS context */
1175
1175
  nestedFrames: NestedFrameGate;
@@ -1204,7 +1204,12 @@ callback: () => Promise<T>): Promise<T>;
1204
1204
  *
1205
1205
  * - Inside a transaction: queued and executed after root transaction commits
1206
1206
  * - Outside a transaction: executed immediately (already "committed")
1207
- * - Nested transactions: callbacks bubble up to root transaction
1207
+ * - Nested transactions: the callback belongs to the savepoint that registered
1208
+ * it. It reaches the root only if that savepoint (and every one around it)
1209
+ * resolved; a savepoint that rolls back drops it, even when the root commits
1210
+ * - A hook registered after its frame (root or savepoint) has settled is
1211
+ * dropped: the frame's queue was handed on, or discarded, when it settled.
1212
+ * Register hooks in the awaited call chain, not from detached work
1208
1213
  * - Callbacks run outside transaction context (new connection for DB access)
1209
1214
  * - Errors are logged but never thrown (commit already succeeded)
1210
1215
  *
@@ -1233,7 +1238,13 @@ declare function onAfterCommit(callback: AfterCommitCallback): void;
1233
1238
  * - Outside a transaction: executed immediately with a WARNING (same rationale as
1234
1239
  * onAfterCommit — there is nothing left to commit), and the abort semantics
1235
1240
  * below do not apply: there is no transaction for a throw to roll back
1236
- * - Nested transactions: callbacks bubble up to root transaction
1241
+ * - Nested transactions: the callback belongs to the savepoint that registered
1242
+ * it. A savepoint that resolved hands it to the root, where it still runs
1243
+ * before the root's COMMIT (never at the savepoint); one that rolls back
1244
+ * drops it, and the root commits without it
1245
+ * - A hook registered after its frame (root or savepoint) has settled is
1246
+ * dropped: the frame's queue was handed on, or discarded, when it settled.
1247
+ * Register hooks in the awaited call chain, not from detached work
1237
1248
  * - Callbacks run in registration order, inside the transaction context
1238
1249
  * (getTransaction() returns the live tx)
1239
1250
  * - The queue is snapshot before the pass, so a callback registered BY a
@@ -1265,13 +1276,30 @@ declare function onBeforeCommit(callback: BeforeCommitCallback): void;
1265
1276
  /**
1266
1277
  * Register a callback to run after the current transaction rolled back
1267
1278
  *
1268
- * - Inside a transaction: queued and executed after the ROOT transaction rolled
1269
- * back, before the causing error leaves runInTransaction / the middleware
1279
+ * - Inside a transaction: queued and executed once the work registered with it
1280
+ * is rolled back, before the causing error leaves runInTransaction / the
1281
+ * middleware
1270
1282
  * - Outside a transaction: no-op with a warning — there is no rollback to wait for
1271
- * - Nested transactions: callbacks bubble up to root transaction. A nested
1272
- * rollback that the root survives does NOT fire them; these hooks are about
1273
- * the root transaction's fate, not a savepoint's
1274
- * - Callbacks run outside the transaction context (new connection for DB access)
1283
+ * - Nested transactions: the callback belongs to the savepoint that registered
1284
+ * it. If that savepoint rolls back, the callback runs right then, awaited
1285
+ * before the error leaves the nested runInTransaction, and never again — the
1286
+ * root's own afterRollback callbacks do not run when the root commits. If the
1287
+ * savepoint resolved, the callback passes to the root and runs only if the
1288
+ * root rolls back
1289
+ * - After a ROOT rollback, callbacks run outside any transaction context (new
1290
+ * connection for DB access). After a SAVEPOINT rollback the root is still
1291
+ * open: getTransactionContext() is the parent's, so DB work in the callback
1292
+ * joins the parent transaction and commits or rolls back with it
1293
+ * - A statement that FAILS in a savepoint's callback aborts the parent
1294
+ * transaction: the error is logged and swallowed here, but the root's next
1295
+ * statement then fails with 25P02 (current transaction is aborted). If the
1296
+ * root runs none, its COMMIT fails with the callback's error. Either way,
1297
+ * everything the root wrote rolls back. A compensation may call
1298
+ * runInTransaction itself — that opens a savepoint on the parent, which
1299
+ * contains such a failure — and hooks it registers belong to the parent
1300
+ * - A hook registered after its frame (root or savepoint) has settled is
1301
+ * dropped: the frame's queue was handed on, or discarded, when it settled.
1302
+ * Register hooks in the awaited call chain, not from detached work
1275
1303
  * - Errors are logged but never thrown: the original error keeps propagating
1276
1304
  * unchanged, never replaced by a callback failure
1277
1305
  *
package/dist/db/index.js CHANGED
@@ -1790,17 +1790,14 @@ function runWithTransaction(tx, txId, callback) {
1790
1790
  } else {
1791
1791
  txLogger.debug("Root transaction context set", { txId, level: newLevel });
1792
1792
  }
1793
- const beforeCommitCallbacks = existingContext?.beforeCommitCallbacks ?? [];
1794
- const afterCommitCallbacks = existingContext?.afterCommitCallbacks ?? [];
1795
- const afterRollbackCallbacks = existingContext?.afterRollbackCallbacks ?? [];
1796
1793
  return asyncContext.run(
1797
1794
  {
1798
1795
  tx,
1799
1796
  txId,
1800
1797
  level: newLevel,
1801
- beforeCommitCallbacks,
1802
- afterCommitCallbacks,
1803
- afterRollbackCallbacks,
1798
+ beforeCommitCallbacks: [],
1799
+ afterCommitCallbacks: [],
1800
+ afterRollbackCallbacks: [],
1804
1801
  nestedFrames: createNestedFrameGate()
1805
1802
  },
1806
1803
  callback
@@ -1872,6 +1869,18 @@ async function runAfterRollbackCallbacks(callbacks, txId, context, enableLogging
1872
1869
  }));
1873
1870
  }
1874
1871
  }
1872
+ function promoteFrameHooks(frame, parent) {
1873
+ parent.beforeCommitCallbacks.push(...frame.beforeCommitCallbacks);
1874
+ parent.afterCommitCallbacks.push(...frame.afterCommitCallbacks);
1875
+ parent.afterRollbackCallbacks.push(...frame.afterRollbackCallbacks);
1876
+ }
1877
+ async function discardFrameHooks(frame, parent, error, txId, context, enableLogging) {
1878
+ await asyncContext.run(
1879
+ { ...parent, nestedFrames: createNestedFrameGate() },
1880
+ () => runAfterRollbackCallbacks(frame.afterRollbackCallbacks, txId, context, enableLogging)
1881
+ ).catch(() => void 0);
1882
+ throw error;
1883
+ }
1875
1884
  async function runInTransaction(callback, options = {}) {
1876
1885
  const defaultTimeout = env.TRANSACTION_TIMEOUT;
1877
1886
  const {
@@ -1977,7 +1986,20 @@ async function runInTransaction(callback, options = {}) {
1977
1986
  const openTransaction = (body) => {
1978
1987
  if (savepointOwner) {
1979
1988
  return savepointOwner.nestedFrames.run(
1980
- () => savepointOwner.tx.transaction(body)
1989
+ () => savepointOwner.tx.transaction(body).then(
1990
+ (result) => {
1991
+ promoteFrameHooks({ beforeCommitCallbacks, afterCommitCallbacks, afterRollbackCallbacks }, savepointOwner);
1992
+ return result;
1993
+ },
1994
+ (error) => discardFrameHooks(
1995
+ { afterRollbackCallbacks },
1996
+ savepointOwner,
1997
+ error,
1998
+ txId,
1999
+ context,
2000
+ enableLogging
2001
+ )
2002
+ )
1981
2003
  );
1982
2004
  }
1983
2005
  if (requiresNew) {
@@ -1995,13 +2017,11 @@ async function runInTransaction(callback, options = {}) {
1995
2017
  await transaction.execute(sql.raw(`SET LOCAL idle_in_transaction_session_timeout = ${idleTimeout}`));
1996
2018
  }
1997
2019
  return await runWithTransaction(transaction, txId, async () => {
1998
- if (!isNested) {
1999
- const ctx = getTransactionContext();
2000
- if (ctx) {
2001
- beforeCommitCallbacks = ctx.beforeCommitCallbacks;
2002
- afterCommitCallbacks = ctx.afterCommitCallbacks;
2003
- afterRollbackCallbacks = ctx.afterRollbackCallbacks;
2004
- }
2020
+ const ctx = getTransactionContext();
2021
+ if (ctx) {
2022
+ beforeCommitCallbacks = ctx.beforeCommitCallbacks;
2023
+ afterCommitCallbacks = ctx.afterCommitCallbacks;
2024
+ afterRollbackCallbacks = ctx.afterRollbackCallbacks;
2005
2025
  }
2006
2026
  const innerResult = await callback(transaction);
2007
2027
  if (!isNested && beforeCommitCallbacks.length > 0) {