@zudojs/transactions 0.1.0 → 1.1.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 +91 -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 +121 -24
  14. package/dist/manager/manager.core.d.ts +34 -3
  15. package/dist/manager/manager.core.js +132 -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 +102 -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 +64 -57
  26. package/dist/transaction/transaction.internal.d.ts +85 -0
  27. package/dist/transaction/transaction.internal.js +82 -0
  28. package/dist/transaction/transaction.participant.d.ts +31 -0
  29. package/dist/transaction/transaction.participant.js +99 -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 +24 -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
@@ -3,89 +3,168 @@
3
3
  *
4
4
  * @module manager/manager
5
5
  */
6
- import { createTransaction } from "../transaction/transaction.core.js";
7
- import { TransactionAdapterError, TransactionRollbackError, } from "../transactionErrors/transactionError.types.js";
8
- import { handlePropagation } from "./manager.propagation.js";
6
+ import { getDefaultContext } from "../context/context.core.js";
7
+ import { internals } from "../transaction/transaction.internal.js";
8
+ import { isTerminal } from "../transaction/transactionStateMachine.js";
9
+ import { TransactionRollbackError } from "../transactionErrors/transactionError.types.js";
9
10
  import { commitTransaction, rollbackTransaction } from "./manager.commit.js";
11
+ import { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
12
+ import { withRetry } from "./manager.retry.js";
13
+ import { createEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
14
+ /** Whether a transaction can no longer change state. */
15
+ function isFinished(transaction) {
16
+ return isTerminal(transaction.state);
17
+ }
10
18
  /**
11
19
  * Create a transaction manager.
12
20
  */
13
21
  export function createTransactionManager(options) {
14
- const { adapter, hooks } = options;
15
- const resolveContext = () => {
16
- if (options.context)
17
- return options.context;
18
- const mod = require("../context/context.core.js");
19
- return mod.getDefaultContext();
20
- };
22
+ const { adapter, hooks, registry } = options;
23
+ const context = options.context ?? getDefaultContext();
24
+ const emit = createEmitter(options.onEvent);
25
+ /** Timers armed for owned transactions, cleared when they complete. */
26
+ const timers = new Map();
27
+ /** Whether the manager completes this handle (root or savepoint). */
28
+ function owns(transaction) {
29
+ return transaction.kind === "root" || transaction.kind === "savepoint";
30
+ }
31
+ /**
32
+ * Arms the timeout for an owned transaction.
33
+ *
34
+ * `begin()` used to validate `timeout` against the adapter's capabilities
35
+ * and then ignore it — only `run()` armed a timer — so a transaction
36
+ * opened by hand never timed out.
37
+ */
38
+ function armTimeout(transaction) {
39
+ const timeout = transaction.options.timeout;
40
+ if (!timeout || timeout <= 0)
41
+ return;
42
+ const timer = setTimeout(() => {
43
+ timers.delete(transaction.id);
44
+ internals(transaction)._markTimedOut();
45
+ transaction.markRollbackOnly("timeout");
46
+ emit(TRANSACTION_EVENTS.TIMED_OUT, transaction);
47
+ }, timeout);
48
+ timers.set(transaction.id, timer);
49
+ }
50
+ /** Clears the timer and registry entry of a completed owned transaction. */
51
+ function release(transaction) {
52
+ if (!owns(transaction))
53
+ return;
54
+ const timer = timers.get(transaction.id);
55
+ if (timer !== undefined) {
56
+ clearTimeout(timer);
57
+ timers.delete(transaction.id);
58
+ }
59
+ registry?.unregister(transaction.id);
60
+ }
21
61
  return {
62
+ /**
63
+ * Begin a transaction, applying the requested propagation mode.
64
+ *
65
+ * With the default `required` propagation inside an existing transaction
66
+ * this returns a participant: a handle that observes the enclosing
67
+ * transaction but never commits it.
68
+ */
22
69
  async begin(opts) {
23
- const context = resolveContext();
24
- const current = context.get();
25
- if (current) {
26
- return handlePropagation(current, opts?.propagation ?? "required", opts, adapter, context, hooks);
27
- }
28
- const transaction = createTransaction(opts);
29
- if (hooks?.beforeBegin)
30
- await hooks.beforeBegin({ transaction });
31
- try {
32
- const handle = await adapter.begin(opts);
33
- transaction._setHandle(handle);
34
- transaction._transition("active");
35
- }
36
- catch (error) {
37
- transaction._transition("failed");
38
- throw new TransactionAdapterError("Failed to begin transaction", error);
39
- }
40
- if (hooks?.afterBegin)
41
- await hooks.afterBegin({ transaction });
42
- if (opts?.timeout && opts.timeout > 0) {
43
- const timer = setTimeout(() => {
44
- transaction._markTimedOut();
45
- transaction.markRollbackOnly("timeout");
46
- }, opts.timeout);
47
- const clearTimer = () => clearTimeout(timer);
48
- transaction.afterCommit(async () => clearTimer());
49
- transaction.afterRollback(async () => clearTimer());
70
+ const propagation = opts?.propagation ?? "required";
71
+ const current = suspendsTransaction(propagation)
72
+ ? undefined
73
+ : context.get();
74
+ const transaction = await resolvePropagation(propagation, {
75
+ current,
76
+ opts,
77
+ adapter,
78
+ hooks,
79
+ emit,
80
+ });
81
+ if (owns(transaction)) {
82
+ registry?.register(transaction);
83
+ armTimeout(transaction);
50
84
  }
51
85
  return transaction;
52
86
  },
87
+ /**
88
+ * Run a callback inside a transaction, committing or rolling back around it.
89
+ *
90
+ * Only a transaction this call opened is completed here: joining an
91
+ * enclosing transaction must not commit it, and a failure inside a
92
+ * participant marks the enclosing transaction rollback-only instead.
93
+ */
53
94
  async run(callback, opts) {
54
- const context = resolveContext();
55
- const transaction = await this.begin(opts);
56
- return context.run(transaction, async () => {
57
- try {
58
- const result = await callback(transaction);
59
- if (transaction.state === "active") {
95
+ // A failed attempt that only JOINED an enclosing transaction has not
96
+ // been rolled back — it marked the enclosing transaction rollback-only
97
+ // — so replaying it would repeat its side effects inside a transaction
98
+ // that can no longer commit. Retry only attempts this call owned.
99
+ let joined = false;
100
+ const retry = opts?.retry;
101
+ const retryOptions = retry === undefined
102
+ ? undefined
103
+ : {
104
+ ...retry,
105
+ shouldRetry: (error, attempt) => !joined && (retry.shouldRetry?.(error, attempt) ?? true),
106
+ };
107
+ return withRetry(retryOptions, async () => {
108
+ const transaction = await this.begin(opts);
109
+ joined = transaction.kind === "participant";
110
+ const body = async () => {
111
+ try {
112
+ const result = await callback(transaction);
60
113
  await this.commit(transaction);
114
+ return result;
61
115
  }
62
- return result;
63
- }
64
- catch (error) {
65
- if (transaction.state === "active" ||
66
- transaction.state === "committing") {
116
+ catch (error) {
67
117
  try {
68
118
  await this.rollback(transaction, error);
69
119
  }
70
120
  catch (rollbackError) {
121
+ if (rollbackError instanceof TransactionRollbackError)
122
+ throw error;
71
123
  throw new TransactionRollbackError(transaction.id, {
72
124
  cause: rollbackError,
73
125
  originalError: error,
74
126
  });
75
127
  }
128
+ throw error;
76
129
  }
77
- throw error;
78
- }
130
+ finally {
131
+ release(transaction);
132
+ }
133
+ };
134
+ return transaction.kind === "none"
135
+ ? context.exit(body)
136
+ : context.run(transaction, body);
79
137
  });
80
138
  },
139
+ /**
140
+ * Commit a transaction. Participants and committed transactions are no-ops.
141
+ *
142
+ * Completing a transaction opened with `begin()` also releases its
143
+ * timeout timer and registry entry; both used to be released only by
144
+ * `run()`, so hand-managed transactions stayed in the registry forever.
145
+ */
81
146
  async commit(transaction) {
82
- return commitTransaction(transaction, adapter, hooks);
147
+ try {
148
+ await commitTransaction(transaction, adapter, hooks, emit);
149
+ }
150
+ finally {
151
+ if (isFinished(transaction))
152
+ release(transaction);
153
+ }
83
154
  },
155
+ /** Roll back a transaction, or mark the joined transaction rollback-only. */
84
156
  async rollback(transaction, reason) {
85
- return rollbackTransaction(transaction, adapter, reason, hooks);
157
+ try {
158
+ await rollbackTransaction(transaction, adapter, reason, hooks, emit);
159
+ }
160
+ finally {
161
+ if (isFinished(transaction))
162
+ release(transaction);
163
+ }
86
164
  },
165
+ /** The transaction in scope for the current async execution, if any. */
87
166
  getCurrent() {
88
- return resolveContext().get();
167
+ return context.get();
89
168
  },
90
169
  };
91
170
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Transaction lifecycle event emission.
3
+ *
4
+ * `TRANSACTION_EVENTS`, `TransactionEvent` and `TransactionEventHandler` were
5
+ * exported from the package barrel from the beginning and nothing ever
6
+ * produced one. This module is what produces them: the manager threads an
7
+ * emitter through begin, commit and rollback, so an observer sees the same
8
+ * lifecycle the state machine does.
9
+ *
10
+ * @module manager/manager.events
11
+ */
12
+ import type { Transaction } from "../transactionTypes/transaction.interface.js";
13
+ import type { TransactionEvent, TransactionEventHandler } from "../transactionTypes/transactionHooks.js";
14
+ import { TRANSACTION_EVENTS } from "../transactionTypes/transactionHooks.js";
15
+ /** Emits lifecycle events, or does nothing when no handler was supplied. */
16
+ export type TransactionEmitter = (type: TransactionEvent["type"], transaction: Transaction, error?: unknown) => void;
17
+ /** An emitter that discards everything. */
18
+ export declare const noopEmitter: TransactionEmitter;
19
+ /**
20
+ * Build an emitter around a handler.
21
+ *
22
+ * A throwing observer must not take the transaction down with it, so handler
23
+ * failures are swallowed the same way a throwing error listener is elsewhere
24
+ * in the framework.
25
+ *
26
+ * @param handler - The observer, or undefined to disable emission.
27
+ * @returns An emitter.
28
+ */
29
+ export declare function createEmitter(handler: TransactionEventHandler | undefined): TransactionEmitter;
30
+ export { TRANSACTION_EVENTS };
31
+ //# sourceMappingURL=manager.events.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Transaction lifecycle event emission.
3
+ *
4
+ * `TRANSACTION_EVENTS`, `TransactionEvent` and `TransactionEventHandler` were
5
+ * exported from the package barrel from the beginning and nothing ever
6
+ * produced one. This module is what produces them: the manager threads an
7
+ * emitter through begin, commit and rollback, so an observer sees the same
8
+ * lifecycle the state machine does.
9
+ *
10
+ * @module manager/manager.events
11
+ */
12
+ import { TRANSACTION_EVENTS } from "../transactionTypes/transactionHooks.js";
13
+ /** An emitter that discards everything. */
14
+ export const noopEmitter = () => { };
15
+ /**
16
+ * Build an emitter around a handler.
17
+ *
18
+ * A throwing observer must not take the transaction down with it, so handler
19
+ * failures are swallowed the same way a throwing error listener is elsewhere
20
+ * in the framework.
21
+ *
22
+ * @param handler - The observer, or undefined to disable emission.
23
+ * @returns An emitter.
24
+ */
25
+ export function createEmitter(handler) {
26
+ if (!handler)
27
+ return noopEmitter;
28
+ return (type, transaction, error) => {
29
+ const event = {
30
+ type,
31
+ transactionId: transaction.id,
32
+ timestamp: Date.now(),
33
+ duration: Date.now() - transaction.startedAt,
34
+ ...(error === undefined ? {} : { error }),
35
+ };
36
+ try {
37
+ handler(event);
38
+ }
39
+ catch {
40
+ // An observer is not allowed to fail the transaction it observes.
41
+ }
42
+ };
43
+ }
44
+ export { TRANSACTION_EVENTS };
45
+ //# sourceMappingURL=manager.events.js.map
@@ -1,15 +1,43 @@
1
1
  /**
2
2
  * Transaction propagation strategies.
3
3
  *
4
+ * Both branches matter: what a mode does when a transaction is already in
5
+ * progress, and what it does when none is. Handling only the first branch is
6
+ * how `mandatory` silently starts a transaction instead of demanding one.
7
+ *
4
8
  * @module manager/manager.propagation
5
9
  */
6
- import type { Transaction } from "../transactionTypes/transaction.interface.js";
7
- import type { TransactionOptions } from "../transactionTypes/transaction.interface.js";
10
+ import type { Transaction, TransactionOptions } from "../transactionTypes/transaction.interface.js";
8
11
  import type { TransactionAdapter } from "../transactionTypes/transactionAdapter.js";
9
- import type { TransactionContext } from "../transactionTypes/transactionAdapter.js";
10
12
  import type { TransactionHooks } from "../transactionTypes/transactionHooks.js";
13
+ import type { TransactionPropagation } from "../transactionTypes/transactionState.js";
14
+ import type { TransactionEmitter } from "./manager.events.js";
15
+ /** Everything a propagation branch may need. */
16
+ export interface PropagationContext {
17
+ readonly current: Transaction | undefined;
18
+ readonly opts: TransactionOptions | undefined;
19
+ readonly adapter: TransactionAdapter;
20
+ readonly hooks: TransactionHooks | undefined;
21
+ /** Lifecycle event emitter. Defaults to discarding events. */
22
+ readonly emit?: TransactionEmitter;
23
+ }
11
24
  /**
12
- * Handle transaction propagation when a transaction already exists.
25
+ * Open a root transaction against the adapter and make it active.
26
+ *
27
+ * @param context - The propagation context.
28
+ * @param parentId - Enclosing transaction id, when nested.
29
+ * @returns An active transaction owning an adapter handle.
30
+ */
31
+ export declare function beginRoot(context: PropagationContext, parentId?: string): Promise<Transaction>;
32
+ /**
33
+ * Resolve a propagation mode to a transaction handle.
34
+ *
35
+ * @param propagation - The requested mode.
36
+ * @param context - The propagation context.
37
+ * @returns The handle the caller should use.
38
+ * @throws {TransactionPropagationError} when the mode's precondition fails.
13
39
  */
14
- export declare function handlePropagation(current: Transaction, propagation: string, opts: TransactionOptions | undefined, adapter: TransactionAdapter, context: TransactionContext, hooks?: TransactionHooks): Promise<Transaction>;
40
+ export declare function resolvePropagation(propagation: TransactionPropagation, context: PropagationContext): Promise<Transaction>;
41
+ /** Whether a propagation mode runs its body outside any transaction. */
42
+ export declare function suspendsTransaction(propagation: TransactionPropagation): boolean;
15
43
  //# sourceMappingURL=manager.propagation.d.ts.map
@@ -1,56 +1,122 @@
1
1
  /**
2
2
  * Transaction propagation strategies.
3
3
  *
4
+ * Both branches matter: what a mode does when a transaction is already in
5
+ * progress, and what it does when none is. Handling only the first branch is
6
+ * how `mandatory` silently starts a transaction instead of demanding one.
7
+ *
4
8
  * @module manager/manager.propagation
5
9
  */
6
10
  import { createTransaction } from "../transaction/transaction.core.js";
7
- import { TransactionPropagationError } from "../transactionErrors/transactionError.types.js";
11
+ import { createNonTransactional, createParticipant, } from "../transaction/transaction.participant.js";
12
+ import { connectionHandle, internals, } from "../transaction/transaction.internal.js";
13
+ import { SavepointError, TransactionCapabilityError, TransactionPropagationError, } from "../transactionErrors/transactionError.types.js";
14
+ import { assertAdapterSupports } from "./manager.capabilities.js";
15
+ import { noopEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
16
+ /**
17
+ * Open a root transaction against the adapter and make it active.
18
+ *
19
+ * @param context - The propagation context.
20
+ * @param parentId - Enclosing transaction id, when nested.
21
+ * @returns An active transaction owning an adapter handle.
22
+ */
23
+ export async function beginRoot(context, parentId) {
24
+ const { adapter, hooks, opts } = context;
25
+ const emit = context.emit ?? noopEmitter;
26
+ assertAdapterSupports(adapter, opts);
27
+ const transaction = createTransaction(opts, parentId, "root");
28
+ if (hooks?.beforeBegin)
29
+ await hooks.beforeBegin({ transaction });
30
+ try {
31
+ internals(transaction)._setHandle(await adapter.begin(opts));
32
+ internals(transaction)._transition("active");
33
+ }
34
+ catch (error) {
35
+ internals(transaction)._transition("failed");
36
+ emit(TRANSACTION_EVENTS.FAILED, transaction, error);
37
+ throw error;
38
+ }
39
+ emit(TRANSACTION_EVENTS.STARTED, transaction);
40
+ if (hooks?.afterBegin)
41
+ await hooks.afterBegin({ transaction });
42
+ return transaction;
43
+ }
44
+ /**
45
+ * Create a nested transaction backed by a savepoint on the enclosing one.
46
+ *
47
+ * @param parent - The enclosing transaction.
48
+ * @param context - The propagation context.
49
+ * @returns An active transaction whose handle names its savepoint.
50
+ */
51
+ async function beginSavepoint(parent, context) {
52
+ const { adapter, hooks, opts } = context;
53
+ const emit = context.emit ?? noopEmitter;
54
+ if (!adapter.capabilities.savepoints || !adapter.createSavepoint) {
55
+ // A missing capability is a configuration mismatch, not a propagation
56
+ // rule violation, and TransactionCapabilityError names what is missing.
57
+ throw new TransactionCapabilityError("savepoints, required by nested transactions");
58
+ }
59
+ const child = createTransaction(opts, parent.id, "savepoint");
60
+ if (hooks?.beforeBegin)
61
+ await hooks.beforeBegin({ transaction: child });
62
+ const savepoint = `sp_${child.id}`;
63
+ // Always the connection: a nested run inside another nested run must
64
+ // create its savepoint on the connection, not on the outer savepoint.
65
+ const parentHandle = connectionHandle(parent);
66
+ try {
67
+ await adapter.createSavepoint(parentHandle, savepoint);
68
+ internals(child)._setHandle({ parent: parentHandle, savepoint });
69
+ internals(child)._transition("active");
70
+ }
71
+ catch (error) {
72
+ internals(child)._transition("failed");
73
+ emit(TRANSACTION_EVENTS.FAILED, child, error);
74
+ throw new SavepointError(`Failed to create savepoint "${savepoint}"`, error);
75
+ }
76
+ emit(TRANSACTION_EVENTS.STARTED, child);
77
+ if (hooks?.afterBegin)
78
+ await hooks.afterBegin({ transaction: child });
79
+ return child;
80
+ }
8
81
  /**
9
- * Handle transaction propagation when a transaction already exists.
82
+ * Resolve a propagation mode to a transaction handle.
83
+ *
84
+ * @param propagation - The requested mode.
85
+ * @param context - The propagation context.
86
+ * @returns The handle the caller should use.
87
+ * @throws {TransactionPropagationError} when the mode's precondition fails.
10
88
  */
11
- export async function handlePropagation(current, propagation, opts, adapter, context, hooks) {
89
+ export async function resolvePropagation(propagation, context) {
90
+ const { current, opts } = context;
12
91
  switch (propagation) {
13
92
  case "required":
14
- return current;
15
- case "requires_new": {
16
- const newTxn = createTransaction(opts);
17
- if (hooks?.beforeBegin)
18
- await hooks.beforeBegin({ transaction: newTxn });
19
- const handle = await adapter.begin(opts);
20
- newTxn._setHandle(handle);
21
- if (hooks?.afterBegin)
22
- await hooks.afterBegin({ transaction: newTxn });
23
- return newTxn;
24
- }
93
+ return current ? createParticipant(current) : beginRoot(context);
94
+ case "requires_new":
95
+ return beginRoot(context);
25
96
  case "supports":
26
- return current;
97
+ return current
98
+ ? createParticipant(current)
99
+ : createNonTransactional(opts);
27
100
  case "not_supported":
28
- return current;
101
+ return createNonTransactional(opts);
29
102
  case "mandatory":
30
- return current;
103
+ if (!current) {
104
+ throw new TransactionPropagationError("Propagation is 'mandatory' but no transaction is in progress");
105
+ }
106
+ return createParticipant(current);
31
107
  case "never":
32
- throw new TransactionPropagationError("Transaction exists but propagation is 'never'");
108
+ if (current) {
109
+ throw new TransactionPropagationError("Transaction exists but propagation is 'never'");
110
+ }
111
+ return createNonTransactional(opts);
33
112
  case "nested":
34
- return createNestedTransaction(current, opts, adapter);
113
+ return current ? beginSavepoint(current, context) : beginRoot(context);
35
114
  default:
36
- throw new TransactionPropagationError(`Unknown propagation: ${propagation}`);
115
+ throw new TransactionPropagationError(`Unknown propagation: ${String(propagation)}`);
37
116
  }
38
117
  }
39
- /**
40
- * Create a nested transaction using savepoints.
41
- */
42
- async function createNestedTransaction(parent, opts, adapter) {
43
- if (!adapter.capabilities.savepoints || !adapter.createSavepoint) {
44
- throw new TransactionPropagationError("Nested transactions require savepoint support");
45
- }
46
- const child = createTransaction(opts, parent.id);
47
- const savepointName = `sp_${child.id}`;
48
- const handle = parent._getHandle();
49
- await adapter.createSavepoint(handle, savepointName);
50
- child._setHandle({
51
- parent: handle,
52
- savepoint: savepointName,
53
- });
54
- return child;
118
+ /** Whether a propagation mode runs its body outside any transaction. */
119
+ export function suspendsTransaction(propagation) {
120
+ return propagation === "not_supported" || propagation === "requires_new";
55
121
  }
56
122
  //# sourceMappingURL=manager.propagation.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Transaction retry.
3
+ *
4
+ * `TransactionOptions.retry` has always been part of the public contract;
5
+ * this is what honours it. Retries re-run the whole unit of work, because a
6
+ * transaction that failed cannot be resumed — only replayed.
7
+ *
8
+ * @module manager/manager.retry
9
+ */
10
+ import type { TransactionRetryOptions } from "../transactionTypes/transaction.interface.js";
11
+ /**
12
+ * Run an operation, replaying it while retries remain.
13
+ *
14
+ * @param options - Retry configuration, or undefined for a single attempt.
15
+ * @param operation - The unit of work, receiving the 1-based attempt number.
16
+ * @returns The operation's result.
17
+ * @throws The last error when every attempt fails.
18
+ */
19
+ export declare function withRetry<T>(options: TransactionRetryOptions | undefined, operation: (attempt: number) => Promise<T>): Promise<T>;
20
+ //# sourceMappingURL=manager.retry.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Transaction retry.
3
+ *
4
+ * `TransactionOptions.retry` has always been part of the public contract;
5
+ * this is what honours it. Retries re-run the whole unit of work, because a
6
+ * transaction that failed cannot be resumed — only replayed.
7
+ *
8
+ * @module manager/manager.retry
9
+ */
10
+ /** Delay before a given attempt, in milliseconds. */
11
+ function delayFor(options, attempt) {
12
+ const base = options.delay ?? 0;
13
+ if (base <= 0)
14
+ return 0;
15
+ return options.backoff === "exponential" ? base * 2 ** (attempt - 1) : base;
16
+ }
17
+ /**
18
+ * Run an operation, replaying it while retries remain.
19
+ *
20
+ * @param options - Retry configuration, or undefined for a single attempt.
21
+ * @param operation - The unit of work, receiving the 1-based attempt number.
22
+ * @returns The operation's result.
23
+ * @throws The last error when every attempt fails.
24
+ */
25
+ export async function withRetry(options, operation) {
26
+ const attempts = Math.max(0, options?.attempts ?? 0) + 1;
27
+ let lastError;
28
+ for (let attempt = 1; attempt <= attempts; attempt++) {
29
+ try {
30
+ return await operation(attempt);
31
+ }
32
+ catch (error) {
33
+ lastError = error;
34
+ const isLast = attempt === attempts;
35
+ const retryable = options?.shouldRetry?.(error, attempt) ?? true;
36
+ if (isLast || !retryable)
37
+ break;
38
+ const delay = delayFor(options, attempt);
39
+ if (delay > 0) {
40
+ await new Promise((resolve) => setTimeout(resolve, delay));
41
+ }
42
+ }
43
+ }
44
+ throw lastError;
45
+ }
46
+ //# sourceMappingURL=manager.retry.js.map
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Core Transaction implementation with state machine.
3
- *
4
- * @module transaction
2
+ * @zudojs/transactions — Transaction Barrel
5
3
  */
6
4
  export { createTransaction } from "./transaction.core.js";
5
+ export { createNonTransactional, createParticipant, } from "./transaction.participant.js";
6
+ export { canTransition, createTransitionFunction, isTerminal, } from "./transactionStateMachine.js";
7
+ export { asSavepointHandle } from "./transaction.internal.js";
8
+ export type { SavepointHandle } from "./transaction.internal.js";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Core Transaction implementation with state machine.
3
- *
4
- * @module transaction
2
+ * @zudojs/transactions — Transaction Barrel
5
3
  */
6
4
  export { createTransaction } from "./transaction.core.js";
5
+ export { createNonTransactional, createParticipant, } from "./transaction.participant.js";
6
+ export { canTransition, createTransitionFunction, isTerminal, } from "./transactionStateMachine.js";
7
+ export { asSavepointHandle } from "./transaction.internal.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -2,8 +2,13 @@
2
2
  * Core Transaction implementation with state machine enforcement.
3
3
  */
4
4
  import type { Transaction, TransactionOptions } from "../transactionTypes/transaction.interface.js";
5
+ import type { TransactionKind } from "../transactionTypes/transactionState.js";
5
6
  /**
6
7
  * Create a new Transaction instance.
8
+ *
9
+ * @param options - Options the transaction was started with.
10
+ * @param parentId - Enclosing transaction id, for nested transactions.
11
+ * @param kind - How this handle relates to the adapter transaction.
7
12
  */
8
- export declare function createTransaction(options?: TransactionOptions, parentId?: string): Transaction;
13
+ export declare function createTransaction(options?: TransactionOptions, parentId?: string, kind?: TransactionKind): Transaction;
9
14
  //# sourceMappingURL=transaction.core.d.ts.map