@zudojs/transactions 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -11
  3. package/dist/adapter/adapter.core.d.ts +4 -0
  4. package/dist/adapter/adapter.core.js +45 -9
  5. package/dist/context/context.core.js +3 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/manager/index.d.ts +7 -0
  9. package/dist/manager/index.js +5 -0
  10. package/dist/manager/manager.capabilities.d.ts +26 -0
  11. package/dist/manager/manager.capabilities.js +41 -0
  12. package/dist/manager/manager.commit.d.ts +19 -2
  13. package/dist/manager/manager.commit.js +112 -24
  14. package/dist/manager/manager.core.d.ts +28 -3
  15. package/dist/manager/manager.core.js +75 -53
  16. package/dist/manager/manager.events.d.ts +31 -0
  17. package/dist/manager/manager.events.js +45 -0
  18. package/dist/manager/manager.propagation.d.ts +33 -5
  19. package/dist/manager/manager.propagation.js +100 -36
  20. package/dist/manager/manager.retry.d.ts +20 -0
  21. package/dist/manager/manager.retry.js +46 -0
  22. package/dist/transaction/index.d.ts +5 -3
  23. package/dist/transaction/index.js +4 -3
  24. package/dist/transaction/transaction.core.d.ts +6 -1
  25. package/dist/transaction/transaction.core.js +60 -57
  26. package/dist/transaction/transaction.internal.d.ts +65 -0
  27. package/dist/transaction/transaction.internal.js +66 -0
  28. package/dist/transaction/transaction.participant.d.ts +31 -0
  29. package/dist/transaction/transaction.participant.js +81 -0
  30. package/dist/transaction/transactionStateMachine.d.ts +10 -0
  31. package/dist/transaction/transactionStateMachine.js +16 -2
  32. package/dist/transactionTypes/index.d.ts +4 -4
  33. package/dist/transactionTypes/transaction.interface.d.ts +19 -1
  34. package/dist/transactionTypes/transactionAdapter.d.ts +7 -9
  35. package/dist/transactionTypes/transactionHooks.d.ts +0 -7
  36. package/dist/transactionTypes/transactionState.d.ts +9 -0
  37. package/dist/utils/utils.helper.d.ts +6 -1
  38. package/dist/utils/utils.helper.js +8 -2
  39. package/package.json +20 -13
  40. package/dist/.tsbuildinfo +0 -1
  41. package/dist/adapter/adapter.core.d.ts.map +0 -1
  42. package/dist/adapter/adapter.core.js.map +0 -1
  43. package/dist/adapter/index.d.ts.map +0 -1
  44. package/dist/adapter/index.js.map +0 -1
  45. package/dist/context/context.core.d.ts.map +0 -1
  46. package/dist/context/context.core.js.map +0 -1
  47. package/dist/context/index.d.ts.map +0 -1
  48. package/dist/context/index.js.map +0 -1
  49. package/dist/hooks/hooks.core.d.ts.map +0 -1
  50. package/dist/hooks/hooks.core.js.map +0 -1
  51. package/dist/hooks/index.d.ts.map +0 -1
  52. package/dist/hooks/index.js.map +0 -1
  53. package/dist/index.d.ts.map +0 -1
  54. package/dist/index.js.map +0 -1
  55. package/dist/manager/index.d.ts.map +0 -1
  56. package/dist/manager/index.js.map +0 -1
  57. package/dist/manager/manager.commit.d.ts.map +0 -1
  58. package/dist/manager/manager.commit.js.map +0 -1
  59. package/dist/manager/manager.core.d.ts.map +0 -1
  60. package/dist/manager/manager.core.js.map +0 -1
  61. package/dist/manager/manager.propagation.d.ts.map +0 -1
  62. package/dist/manager/manager.propagation.js.map +0 -1
  63. package/dist/registry/index.d.ts.map +0 -1
  64. package/dist/registry/index.js.map +0 -1
  65. package/dist/registry/registry.core.d.ts.map +0 -1
  66. package/dist/registry/registry.core.js.map +0 -1
  67. package/dist/transaction/index.d.ts.map +0 -1
  68. package/dist/transaction/index.js.map +0 -1
  69. package/dist/transaction/transaction.core.d.ts.map +0 -1
  70. package/dist/transaction/transaction.core.js.map +0 -1
  71. package/dist/transaction/transactionStateMachine.d.ts.map +0 -1
  72. package/dist/transaction/transactionStateMachine.js.map +0 -1
  73. package/dist/transactionErrors/index.d.ts.map +0 -1
  74. package/dist/transactionErrors/index.js.map +0 -1
  75. package/dist/transactionErrors/transactionError.base.d.ts.map +0 -1
  76. package/dist/transactionErrors/transactionError.base.js.map +0 -1
  77. package/dist/transactionErrors/transactionError.types.d.ts.map +0 -1
  78. package/dist/transactionErrors/transactionError.types.js.map +0 -1
  79. package/dist/transactionTypes/index.d.ts.map +0 -1
  80. package/dist/transactionTypes/index.js.map +0 -1
  81. package/dist/transactionTypes/transaction.interface.d.ts.map +0 -1
  82. package/dist/transactionTypes/transaction.interface.js.map +0 -1
  83. package/dist/transactionTypes/transactionAdapter.d.ts.map +0 -1
  84. package/dist/transactionTypes/transactionAdapter.js.map +0 -1
  85. package/dist/transactionTypes/transactionHooks.d.ts.map +0 -1
  86. package/dist/transactionTypes/transactionHooks.js.map +0 -1
  87. package/dist/transactionTypes/transactionState.d.ts.map +0 -1
  88. package/dist/transactionTypes/transactionState.js.map +0 -1
  89. package/dist/utils/index.d.ts.map +0 -1
  90. package/dist/utils/index.js.map +0 -1
  91. package/dist/utils/utils.helper.d.ts.map +0 -1
  92. package/dist/utils/utils.helper.js.map +0 -1
@@ -2,18 +2,36 @@
2
2
  * Core Transaction implementation with state machine enforcement.
3
3
  */
4
4
  import { randomBytes } from "node:crypto";
5
- import { TransactionStateError, TransactionCommitError, TransactionRollbackError, } from "../transactionErrors/transactionError.types.js";
6
- import { createTransitionFunction } from "./transactionStateMachine.js";
5
+ import { TransactionRollbackError, TransactionStateError, } from "../transactionErrors/transactionError.types.js";
6
+ import { canTransition, createTransitionFunction, } from "./transactionStateMachine.js";
7
+ import { attachInternals } from "./transaction.internal.js";
7
8
  /**
8
9
  * Generate a unique transaction ID.
9
10
  */
10
11
  function generateTransactionId() {
11
12
  return `txn_${randomBytes(16).toString("hex")}`;
12
13
  }
14
+ /** Runs callbacks in order, collecting failures rather than aborting. */
15
+ async function runCallbacks(callbacks) {
16
+ const errors = [];
17
+ for (const callback of callbacks.splice(0)) {
18
+ try {
19
+ await callback();
20
+ }
21
+ catch (error) {
22
+ errors.push(error);
23
+ }
24
+ }
25
+ return errors;
26
+ }
13
27
  /**
14
28
  * Create a new Transaction instance.
29
+ *
30
+ * @param options - Options the transaction was started with.
31
+ * @param parentId - Enclosing transaction id, for nested transactions.
32
+ * @param kind - How this handle relates to the adapter transaction.
15
33
  */
16
- export function createTransaction(options = {}, parentId) {
34
+ export function createTransaction(options = {}, parentId, kind = "root") {
17
35
  let state = "pending";
18
36
  let rollbackOnly = false;
19
37
  let rollbackOnlyReason;
@@ -21,14 +39,11 @@ export function createTransaction(options = {}, parentId) {
21
39
  const afterCommitCallbacks = [];
22
40
  const afterRollbackCallbacks = [];
23
41
  let handle;
24
- const setHandle = (h) => {
25
- handle = h;
26
- };
27
- const getHandle = () => handle;
28
- const transition = createTransitionFunction(() => state, (s) => {
29
- state = s;
42
+ const transition = createTransitionFunction(() => state, (next) => {
43
+ state = next;
30
44
  });
31
45
  const metadata = new Map(options.metadata ? Object.entries(options.metadata) : []);
46
+ const frozenOptions = Object.freeze({ ...options });
32
47
  const id = generateTransactionId();
33
48
  const startedAt = Date.now();
34
49
  const txn = {
@@ -38,75 +53,62 @@ export function createTransaction(options = {}, parentId) {
38
53
  get parentId() {
39
54
  return parentId;
40
55
  },
56
+ get kind() {
57
+ return kind;
58
+ },
41
59
  get state() {
42
60
  return state;
43
61
  },
44
62
  get options() {
45
- return Object.freeze({ ...options });
63
+ return frozenOptions;
46
64
  },
47
65
  get startedAt() {
48
66
  return startedAt;
49
67
  },
50
68
  get metadata() {
51
- return metadata;
69
+ return new Map(metadata);
52
70
  },
53
71
  get timedOut() {
54
72
  return timedOut;
55
73
  },
74
+ /**
75
+ * Mark the transaction committed and run its after-commit callbacks.
76
+ *
77
+ * Refuses outright when the transaction is rollback-only: a caller must
78
+ * never be able to mistake a rollback for a commit.
79
+ */
56
80
  async commit() {
81
+ if (state === "committed")
82
+ return;
57
83
  if (state !== "active") {
58
84
  throw new TransactionStateError(state, "commit");
59
85
  }
60
86
  if (rollbackOnly) {
61
- await txn.rollback(rollbackOnlyReason ?? "marked rollback-only");
62
- return;
87
+ throw new TransactionRollbackError(id, {
88
+ originalError: rollbackOnlyReason ?? "marked rollback-only",
89
+ });
63
90
  }
64
91
  transition("committing");
65
- try {
66
- transition("committed");
67
- for (const cb of afterCommitCallbacks) {
68
- try {
69
- await cb();
70
- }
71
- catch {
72
- /* swallow */
73
- }
74
- }
75
- afterCommitCallbacks.length = 0;
76
- afterRollbackCallbacks.length = 0;
77
- }
78
- catch (error) {
79
- transition("failed");
80
- throw new TransactionCommitError(id, error);
81
- }
92
+ transition("committed");
93
+ afterRollbackCallbacks.length = 0;
94
+ await runCallbacks(afterCommitCallbacks);
82
95
  },
83
96
  async rollback(reason) {
84
- if (state === "committed" ||
85
- state === "rolled_back" ||
86
- state === "failed") {
97
+ if (state === "rolled_back" || state === "failed")
87
98
  return;
99
+ if (state === "committed") {
100
+ throw new TransactionStateError(state, "rollback");
88
101
  }
89
- if (state !== "active" && state !== "committing" && state !== "pending") {
102
+ if (!canTransition(state, "rolling_back")) {
90
103
  throw new TransactionStateError(state, "rollback");
91
104
  }
92
105
  transition("rolling_back");
93
- try {
94
- transition("rolled_back");
95
- for (const cb of afterRollbackCallbacks) {
96
- try {
97
- await cb();
98
- }
99
- catch {
100
- /* swallow */
101
- }
102
- }
103
- afterCommitCallbacks.length = 0;
104
- afterRollbackCallbacks.length = 0;
105
- }
106
- catch (error) {
107
- transition("failed");
106
+ transition("rolled_back");
107
+ afterCommitCallbacks.length = 0;
108
+ const errors = await runCallbacks(afterRollbackCallbacks);
109
+ if (errors.length > 0) {
108
110
  throw new TransactionRollbackError(id, {
109
- cause: error,
111
+ cause: new AggregateError(errors, "after-rollback callback failures"),
110
112
  originalError: reason,
111
113
  });
112
114
  }
@@ -125,15 +127,16 @@ export function createTransaction(options = {}, parentId) {
125
127
  afterRollbackCallbacks.push(callback);
126
128
  },
127
129
  };
128
- return Object.assign(txn, {
129
- /** @internal */ _setHandle: setHandle,
130
- /** @internal */ _getHandle: getHandle,
131
- /** @internal */ _transition: transition,
132
- /** @internal */ _markTimedOut: () => {
130
+ return attachInternals(txn, {
131
+ _setHandle: (next) => {
132
+ handle = next;
133
+ },
134
+ _getHandle: () => handle,
135
+ _transition: transition,
136
+ _markTimedOut: () => {
133
137
  timedOut = true;
134
138
  },
135
- /** @internal */ _getAfterCommitCallbacks: () => afterCommitCallbacks,
136
- /** @internal */ _getAfterRollbackCallbacks: () => afterRollbackCallbacks,
139
+ _getRollbackOnlyReason: () => rollbackOnlyReason,
137
140
  });
138
141
  }
139
142
  //# sourceMappingURL=transaction.core.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Typed access to the manager-only surface of a Transaction.
3
+ *
4
+ * `createTransaction` attaches a handful of underscore-prefixed helpers the
5
+ * manager needs and consumers must not use. Routing every access through this
6
+ * module keeps the casts in one place, so renaming a helper is a compile
7
+ * error rather than a runtime `undefined is not a function`.
8
+ *
9
+ * @module transaction/transaction.internal
10
+ */
11
+ import type { Transaction } from "../transactionTypes/transaction.interface.js";
12
+ import type { TransactionState } from "../transactionTypes/transactionState.js";
13
+ /**
14
+ * Key under which the internals are attached.
15
+ *
16
+ * Deliberately not re-exported from the package barrel: the manager imports
17
+ * this module directly, while a consumer holding only the public API has no
18
+ * way to name the property and therefore cannot drive the state machine or
19
+ * steal the adapter handle.
20
+ */
21
+ export declare const TRANSACTION_INTERNALS: unique symbol;
22
+ /** The manager-only operations attached to every transaction. */
23
+ export interface TransactionInternals {
24
+ /** Store the adapter handle this transaction commits against. */
25
+ _setHandle(handle: unknown): void;
26
+ /** Read the adapter handle, if one was stored. */
27
+ _getHandle(): unknown;
28
+ /** Drive the state machine directly. */
29
+ _transition(state: TransactionState): void;
30
+ /** Record that the transaction outlived its timeout. */
31
+ _markTimedOut(): void;
32
+ /** The reason supplied to `markRollbackOnly`, if any. */
33
+ _getRollbackOnlyReason(): unknown;
34
+ }
35
+ /**
36
+ * Access the manager-only surface of a transaction.
37
+ *
38
+ * @param transaction - A transaction created by `createTransaction`.
39
+ * @returns The internal operations.
40
+ * @throws {TypeError} when the transaction did not come from this package.
41
+ */
42
+ export declare function internals(transaction: Transaction): TransactionInternals;
43
+ /**
44
+ * Attach the manager-only surface to a freshly created transaction.
45
+ *
46
+ * @param transaction - The transaction to extend.
47
+ * @param operations - The internal operations to attach.
48
+ * @returns The same transaction.
49
+ */
50
+ export declare function attachInternals<T extends Transaction>(transaction: T, operations: TransactionInternals): T;
51
+ /** A savepoint handle stored on a nested transaction. */
52
+ export interface SavepointHandle {
53
+ /** The enclosing transaction's adapter handle. */
54
+ readonly parent: unknown;
55
+ /** The savepoint name created on that handle. */
56
+ readonly savepoint: string;
57
+ }
58
+ /**
59
+ * Narrow an adapter handle to a savepoint handle.
60
+ *
61
+ * @param handle - The handle stored on a transaction.
62
+ * @returns The savepoint handle, or undefined when it is a plain handle.
63
+ */
64
+ export declare function asSavepointHandle(handle: unknown): SavepointHandle | undefined;
65
+ //# sourceMappingURL=transaction.internal.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Typed access to the manager-only surface of a Transaction.
3
+ *
4
+ * `createTransaction` attaches a handful of underscore-prefixed helpers the
5
+ * manager needs and consumers must not use. Routing every access through this
6
+ * module keeps the casts in one place, so renaming a helper is a compile
7
+ * error rather than a runtime `undefined is not a function`.
8
+ *
9
+ * @module transaction/transaction.internal
10
+ */
11
+ /**
12
+ * Key under which the internals are attached.
13
+ *
14
+ * Deliberately not re-exported from the package barrel: the manager imports
15
+ * this module directly, while a consumer holding only the public API has no
16
+ * way to name the property and therefore cannot drive the state machine or
17
+ * steal the adapter handle.
18
+ */
19
+ export const TRANSACTION_INTERNALS = Symbol("zudojs.transaction.internals");
20
+ /**
21
+ * Access the manager-only surface of a transaction.
22
+ *
23
+ * @param transaction - A transaction created by `createTransaction`.
24
+ * @returns The internal operations.
25
+ * @throws {TypeError} when the transaction did not come from this package.
26
+ */
27
+ export function internals(transaction) {
28
+ const carrier = transaction;
29
+ const found = carrier[TRANSACTION_INTERNALS];
30
+ if (!found) {
31
+ throw new TypeError("Transaction was not created by @zudojs/transactions and has no internals.");
32
+ }
33
+ return found;
34
+ }
35
+ /**
36
+ * Attach the manager-only surface to a freshly created transaction.
37
+ *
38
+ * @param transaction - The transaction to extend.
39
+ * @param operations - The internal operations to attach.
40
+ * @returns The same transaction.
41
+ */
42
+ export function attachInternals(transaction, operations) {
43
+ Object.defineProperty(transaction, TRANSACTION_INTERNALS, {
44
+ value: operations,
45
+ enumerable: false,
46
+ writable: false,
47
+ configurable: false,
48
+ });
49
+ return transaction;
50
+ }
51
+ /**
52
+ * Narrow an adapter handle to a savepoint handle.
53
+ *
54
+ * @param handle - The handle stored on a transaction.
55
+ * @returns The savepoint handle, or undefined when it is a plain handle.
56
+ */
57
+ export function asSavepointHandle(handle) {
58
+ if (typeof handle === "object" &&
59
+ handle !== null &&
60
+ "savepoint" in handle &&
61
+ typeof handle.savepoint === "string") {
62
+ return handle;
63
+ }
64
+ return undefined;
65
+ }
66
+ //# sourceMappingURL=transaction.internal.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Participant and non-transactional transaction handles.
3
+ *
4
+ * A participant is what `required`, `supports` and `mandatory` hand back when
5
+ * a transaction is already in progress. It delegates every observation to the
6
+ * transaction it joined but owns nothing: committing it is a no-op, and
7
+ * rolling it back marks the enclosing transaction rollback-only rather than
8
+ * discarding work the enclosing scope has not finished with.
9
+ *
10
+ * @module transaction/transaction.participant
11
+ */
12
+ import type { Transaction, TransactionOptions } from "../transactionTypes/transaction.interface.js";
13
+ /**
14
+ * Create a handle that joins an in-progress transaction.
15
+ *
16
+ * @param parent - The transaction being joined.
17
+ * @returns A participant handle delegating to `parent`.
18
+ */
19
+ export declare function createParticipant(parent: Transaction): Transaction;
20
+ /**
21
+ * Create a handle for a deliberately non-transactional scope.
22
+ *
23
+ * Returned by `supports` and `never` when nothing is in progress, and by
24
+ * `not_supported` always. It moves through the normal lifecycle so callers can
25
+ * treat it uniformly, but never touches an adapter.
26
+ *
27
+ * @param options - Options the scope was opened with.
28
+ * @returns A transaction handle bound to no adapter transaction.
29
+ */
30
+ export declare function createNonTransactional(options?: TransactionOptions): Transaction;
31
+ //# sourceMappingURL=transaction.participant.d.ts.map
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Participant and non-transactional transaction handles.
3
+ *
4
+ * A participant is what `required`, `supports` and `mandatory` hand back when
5
+ * a transaction is already in progress. It delegates every observation to the
6
+ * transaction it joined but owns nothing: committing it is a no-op, and
7
+ * rolling it back marks the enclosing transaction rollback-only rather than
8
+ * discarding work the enclosing scope has not finished with.
9
+ *
10
+ * @module transaction/transaction.participant
11
+ */
12
+ import { createTransaction } from "./transaction.core.js";
13
+ import { internals } from "./transaction.internal.js";
14
+ /**
15
+ * Create a handle that joins an in-progress transaction.
16
+ *
17
+ * @param parent - The transaction being joined.
18
+ * @returns A participant handle delegating to `parent`.
19
+ */
20
+ export function createParticipant(parent) {
21
+ return Object.freeze({
22
+ get id() {
23
+ return parent.id;
24
+ },
25
+ get parentId() {
26
+ return parent.parentId;
27
+ },
28
+ get kind() {
29
+ return "participant";
30
+ },
31
+ get state() {
32
+ return parent.state;
33
+ },
34
+ get options() {
35
+ return parent.options;
36
+ },
37
+ get startedAt() {
38
+ return parent.startedAt;
39
+ },
40
+ get metadata() {
41
+ return parent.metadata;
42
+ },
43
+ get timedOut() {
44
+ return parent.timedOut;
45
+ },
46
+ /** No-op: the transaction is committed by whoever opened it. */
47
+ async commit() { },
48
+ /** Marks the joined transaction rollback-only. */
49
+ async rollback(reason) {
50
+ parent.markRollbackOnly(reason ?? "participant rolled back");
51
+ },
52
+ markRollbackOnly(reason) {
53
+ parent.markRollbackOnly(reason);
54
+ },
55
+ isRollbackOnly() {
56
+ return parent.isRollbackOnly();
57
+ },
58
+ afterCommit(callback) {
59
+ parent.afterCommit(callback);
60
+ },
61
+ afterRollback(callback) {
62
+ parent.afterRollback(callback);
63
+ },
64
+ });
65
+ }
66
+ /**
67
+ * Create a handle for a deliberately non-transactional scope.
68
+ *
69
+ * Returned by `supports` and `never` when nothing is in progress, and by
70
+ * `not_supported` always. It moves through the normal lifecycle so callers can
71
+ * treat it uniformly, but never touches an adapter.
72
+ *
73
+ * @param options - Options the scope was opened with.
74
+ * @returns A transaction handle bound to no adapter transaction.
75
+ */
76
+ export function createNonTransactional(options = {}) {
77
+ const txn = createTransaction(options, undefined, "none");
78
+ internals(txn)._transition("active");
79
+ return txn;
80
+ }
81
+ //# sourceMappingURL=transaction.participant.js.map
@@ -6,4 +6,14 @@ import type { TransactionState } from "../transactionTypes/transactionState.js";
6
6
  * Creates a state transition function for a transaction.
7
7
  */
8
8
  export declare function createTransitionFunction(getState: () => TransactionState, setState: (state: TransactionState) => void): (to: TransactionState) => void;
9
+ /**
10
+ * Whether a state transition is permitted.
11
+ *
12
+ * Error handlers use this before transitioning, so a failure raised from a
13
+ * terminal state cannot be replaced by a TransactionStateError about the
14
+ * transition itself.
15
+ */
16
+ export declare function canTransition(from: TransactionState, to: TransactionState): boolean;
17
+ /** Whether a state admits no further transitions. */
18
+ export declare function isTerminal(state: TransactionState): boolean;
9
19
  //# sourceMappingURL=transactionStateMachine.d.ts.map
@@ -4,7 +4,7 @@
4
4
  import { TransactionStateError } from "../transactionErrors/transactionError.types.js";
5
5
  /** Valid state transitions. */
6
6
  const VALID_TRANSITIONS = {
7
- pending: ["active", "failed"],
7
+ pending: ["active", "rolling_back", "failed"],
8
8
  active: ["committing", "rolling_back", "failed"],
9
9
  committing: ["committed", "failed", "rolling_back"],
10
10
  committed: [],
@@ -18,10 +18,24 @@ const VALID_TRANSITIONS = {
18
18
  export function createTransitionFunction(getState, setState) {
19
19
  return (to) => {
20
20
  const current = getState();
21
- if (!VALID_TRANSITIONS[current]?.includes(to)) {
21
+ if (!canTransition(current, to)) {
22
22
  throw new TransactionStateError(current, `transition to ${to}`);
23
23
  }
24
24
  setState(to);
25
25
  };
26
26
  }
27
+ /**
28
+ * Whether a state transition is permitted.
29
+ *
30
+ * Error handlers use this before transitioning, so a failure raised from a
31
+ * terminal state cannot be replaced by a TransactionStateError about the
32
+ * transition itself.
33
+ */
34
+ export function canTransition(from, to) {
35
+ return VALID_TRANSITIONS[from]?.includes(to) ?? false;
36
+ }
37
+ /** Whether a state admits no further transitions. */
38
+ export function isTerminal(state) {
39
+ return (VALID_TRANSITIONS[state]?.length ?? 0) === 0;
40
+ }
27
41
  //# sourceMappingURL=transactionStateMachine.js.map
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * @module transactionTypes
5
5
  */
6
- export { type TransactionState, type TransactionPropagation, type TransactionIsolationLevel, } from "./transactionState.js";
7
- export { type Transaction, type TransactionOptions, type TransactionRetryOptions, } from "./transaction.interface.js";
8
- export { type TransactionHandle, type TransactionAdapterCapabilities, type TransactionAdapter, type Savepoint, type TransactionContext, } from "./transactionAdapter.js";
9
- export { type TransactionHookContext, type TransactionErrorContext, type TransactionHooks, type TransactionRegistry, type TransactionResult, TRANSACTION_EVENTS, type TransactionEvent, type TransactionEventHandler, } from "./transactionHooks.js";
6
+ export { type TransactionState, type TransactionKind, type TransactionPropagation, type TransactionIsolationLevel, } from "./transactionState.js";
7
+ export { type Transaction, type TransactionOptions, type TransactionRetryOptions, type TransactionRetryPredicate, } from "./transaction.interface.js";
8
+ export { type TransactionHandle, type TransactionAdapterCapabilities, type TransactionAdapter, type TransactionContext, } from "./transactionAdapter.js";
9
+ export { type TransactionHookContext, type TransactionErrorContext, type TransactionHooks, type TransactionRegistry, TRANSACTION_EVENTS, type TransactionEvent, type TransactionEventHandler, } from "./transactionHooks.js";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module transactionTypes/transaction
5
5
  */
6
- import type { TransactionState, TransactionPropagation, TransactionIsolationLevel } from "./transactionState.js";
6
+ import type { TransactionState, TransactionKind, TransactionPropagation, TransactionIsolationLevel } from "./transactionState.js";
7
7
  /** Options for starting a transaction. */
8
8
  export interface TransactionOptions {
9
9
  /** Isolation level. */
@@ -21,6 +21,8 @@ export interface TransactionOptions {
21
21
  /** Retry configuration. */
22
22
  readonly retry?: TransactionRetryOptions;
23
23
  }
24
+ /** Decides whether a failed attempt is worth retrying. */
25
+ export type TransactionRetryPredicate = (error: unknown, attempt: number) => boolean;
24
26
  /** Retry configuration for transaction failures. */
25
27
  export interface TransactionRetryOptions {
26
28
  /** Maximum number of retry attempts. Defaults to 0 (no retry). */
@@ -29,6 +31,14 @@ export interface TransactionRetryOptions {
29
31
  readonly delay?: number;
30
32
  /** Backoff strategy. */
31
33
  readonly backoff?: "fixed" | "exponential";
34
+ /**
35
+ * Whether a given failure should be retried.
36
+ *
37
+ * Defaults to retrying everything. Supply a predicate to limit retries to
38
+ * the failures that are actually transient, such as serialization failures
39
+ * and deadlocks.
40
+ */
41
+ readonly shouldRetry?: TransactionRetryPredicate;
32
42
  }
33
43
  /** The core transaction interface. */
34
44
  export interface Transaction {
@@ -36,6 +46,14 @@ export interface Transaction {
36
46
  readonly id: string;
37
47
  /** Parent transaction ID (for nested transactions). */
38
48
  readonly parentId?: string;
49
+ /**
50
+ * How this handle relates to the underlying adapter transaction.
51
+ *
52
+ * A `participant` joined an enclosing transaction and must not commit it;
53
+ * `none` marks a non-transactional scope opened by the `supports`,
54
+ * `not_supported` or `never` propagation modes.
55
+ */
56
+ readonly kind: TransactionKind;
39
57
  /** Current lifecycle state. */
40
58
  readonly state: TransactionState;
41
59
  /** Options used to start this transaction. */
@@ -37,20 +37,18 @@ export interface TransactionAdapter {
37
37
  /** Release a savepoint (optional). */
38
38
  releaseSavepoint?(handle: TransactionHandle, name: string): Promise<void>;
39
39
  }
40
- /** A savepoint within a transaction. */
41
- export interface Savepoint {
42
- /** Savepoint name. */
43
- readonly name: string;
44
- /** Rollback to this savepoint. */
45
- rollback(): Promise<void>;
46
- /** Release this savepoint. */
47
- release(): Promise<void>;
48
- }
49
40
  /** Transaction context for async propagation. */
50
41
  export interface TransactionContext {
51
42
  /** Get the current active transaction, if any. */
52
43
  get(): Transaction | undefined;
53
44
  /** Run a callback within a transaction context. */
54
45
  run<T>(transaction: Transaction, callback: () => Promise<T>): Promise<T>;
46
+ /**
47
+ * Run a callback with no transaction in scope.
48
+ *
49
+ * Used by the `not_supported` and `requires_new` propagation modes, which
50
+ * must suspend an enclosing transaction rather than join it.
51
+ */
52
+ exit<T>(callback: () => Promise<T>): Promise<T>;
55
53
  }
56
54
  //# sourceMappingURL=transactionAdapter.d.ts.map
@@ -33,13 +33,6 @@ export interface TransactionRegistry {
33
33
  get(transactionId: string): Transaction | undefined;
34
34
  getActive(): readonly Transaction[];
35
35
  }
36
- /** Result of a transaction.run() call. */
37
- export interface TransactionResult<T> {
38
- readonly value: T;
39
- readonly transactionId: string;
40
- readonly committed: boolean;
41
- readonly duration: number;
42
- }
43
36
  /** Transaction event types. */
44
37
  export declare const TRANSACTION_EVENTS: {
45
38
  readonly STARTED: "transaction.started";
@@ -7,6 +7,15 @@
7
7
  export type TransactionState = "pending" | "active" | "committing" | "committed" | "rolling_back" | "rolled_back" | "failed";
8
8
  /** Transaction propagation strategies for nested calls. */
9
9
  export type TransactionPropagation = "required" | "requires_new" | "supports" | "not_supported" | "mandatory" | "never" | "nested";
10
+ /**
11
+ * How a transaction handle relates to the underlying adapter transaction.
12
+ *
13
+ * The manager routes commit and rollback by kind: only a `root` owns an
14
+ * adapter transaction, a `participant` must never commit the transaction it
15
+ * joined, a `savepoint` resolves to its savepoint rather than the connection,
16
+ * and `none` marks a deliberately non-transactional scope.
17
+ */
18
+ export type TransactionKind = "root" | "participant" | "savepoint" | "none";
10
19
  /** Database isolation levels. */
11
20
  export type TransactionIsolationLevel = "read_uncommitted" | "read_committed" | "repeatable_read" | "serializable";
12
21
  //# sourceMappingURL=transactionState.d.ts.map
@@ -6,7 +6,12 @@
6
6
  import type { Transaction } from "../transactionTypes/transaction.interface.js";
7
7
  import type { TransactionState } from "../transactionTypes/transactionState.js";
8
8
  /**
9
- * Check if a transaction is in a terminal state (committed, rolled_back, or failed).
9
+ * Check if a transaction is in a terminal state.
10
+ *
11
+ * Delegates to {@link isTerminal}, which derives the answer from the state
12
+ * machine's transition table. This used to hardcode its own list of terminal
13
+ * states, so the two exported predicates could disagree the moment a state
14
+ * was added.
10
15
  */
11
16
  export declare function isTerminalState(state: TransactionState): boolean;
12
17
  /**
@@ -3,11 +3,17 @@
3
3
  *
4
4
  * @module utils/utils
5
5
  */
6
+ import { isTerminal } from "../transaction/transactionStateMachine.js";
6
7
  /**
7
- * Check if a transaction is in a terminal state (committed, rolled_back, or failed).
8
+ * Check if a transaction is in a terminal state.
9
+ *
10
+ * Delegates to {@link isTerminal}, which derives the answer from the state
11
+ * machine's transition table. This used to hardcode its own list of terminal
12
+ * states, so the two exported predicates could disagree the moment a state
13
+ * was added.
8
14
  */
9
15
  export function isTerminalState(state) {
10
- return state === "committed" || state === "rolled_back" || state === "failed";
16
+ return isTerminal(state);
11
17
  }
12
18
  /**
13
19
  * Check if a transaction can still be modified (active or pending).