@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.
- package/LICENSE +21 -0
- package/README.md +82 -11
- package/dist/adapter/adapter.core.d.ts +4 -0
- package/dist/adapter/adapter.core.js +45 -9
- package/dist/context/context.core.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/manager/index.d.ts +7 -0
- package/dist/manager/index.js +5 -0
- package/dist/manager/manager.capabilities.d.ts +26 -0
- package/dist/manager/manager.capabilities.js +41 -0
- package/dist/manager/manager.commit.d.ts +19 -2
- package/dist/manager/manager.commit.js +112 -24
- package/dist/manager/manager.core.d.ts +28 -3
- package/dist/manager/manager.core.js +75 -53
- package/dist/manager/manager.events.d.ts +31 -0
- package/dist/manager/manager.events.js +45 -0
- package/dist/manager/manager.propagation.d.ts +33 -5
- package/dist/manager/manager.propagation.js +100 -36
- package/dist/manager/manager.retry.d.ts +20 -0
- package/dist/manager/manager.retry.js +46 -0
- package/dist/transaction/index.d.ts +5 -3
- package/dist/transaction/index.js +4 -3
- package/dist/transaction/transaction.core.d.ts +6 -1
- package/dist/transaction/transaction.core.js +60 -57
- package/dist/transaction/transaction.internal.d.ts +65 -0
- package/dist/transaction/transaction.internal.js +66 -0
- package/dist/transaction/transaction.participant.d.ts +31 -0
- package/dist/transaction/transaction.participant.js +81 -0
- package/dist/transaction/transactionStateMachine.d.ts +10 -0
- package/dist/transaction/transactionStateMachine.js +16 -2
- package/dist/transactionTypes/index.d.ts +4 -4
- package/dist/transactionTypes/transaction.interface.d.ts +19 -1
- package/dist/transactionTypes/transactionAdapter.d.ts +7 -9
- package/dist/transactionTypes/transactionHooks.d.ts +0 -7
- package/dist/transactionTypes/transactionState.d.ts +9 -0
- package/dist/utils/utils.helper.d.ts +6 -1
- package/dist/utils/utils.helper.js +8 -2
- package/package.json +20 -13
- package/dist/.tsbuildinfo +0 -1
- package/dist/adapter/adapter.core.d.ts.map +0 -1
- package/dist/adapter/adapter.core.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js.map +0 -1
- package/dist/context/context.core.d.ts.map +0 -1
- package/dist/context/context.core.js.map +0 -1
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js.map +0 -1
- package/dist/hooks/hooks.core.d.ts.map +0 -1
- package/dist/hooks/hooks.core.js.map +0 -1
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/manager/index.d.ts.map +0 -1
- package/dist/manager/index.js.map +0 -1
- package/dist/manager/manager.commit.d.ts.map +0 -1
- package/dist/manager/manager.commit.js.map +0 -1
- package/dist/manager/manager.core.d.ts.map +0 -1
- package/dist/manager/manager.core.js.map +0 -1
- package/dist/manager/manager.propagation.d.ts.map +0 -1
- package/dist/manager/manager.propagation.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/registry.core.d.ts.map +0 -1
- package/dist/registry/registry.core.js.map +0 -1
- package/dist/transaction/index.d.ts.map +0 -1
- package/dist/transaction/index.js.map +0 -1
- package/dist/transaction/transaction.core.d.ts.map +0 -1
- package/dist/transaction/transaction.core.js.map +0 -1
- package/dist/transaction/transactionStateMachine.d.ts.map +0 -1
- package/dist/transaction/transactionStateMachine.js.map +0 -1
- package/dist/transactionErrors/index.d.ts.map +0 -1
- package/dist/transactionErrors/index.js.map +0 -1
- package/dist/transactionErrors/transactionError.base.d.ts.map +0 -1
- package/dist/transactionErrors/transactionError.base.js.map +0 -1
- package/dist/transactionErrors/transactionError.types.d.ts.map +0 -1
- package/dist/transactionErrors/transactionError.types.js.map +0 -1
- package/dist/transactionTypes/index.d.ts.map +0 -1
- package/dist/transactionTypes/index.js.map +0 -1
- package/dist/transactionTypes/transaction.interface.d.ts.map +0 -1
- package/dist/transactionTypes/transaction.interface.js.map +0 -1
- package/dist/transactionTypes/transactionAdapter.d.ts.map +0 -1
- package/dist/transactionTypes/transactionAdapter.js.map +0 -1
- package/dist/transactionTypes/transactionHooks.d.ts.map +0 -1
- package/dist/transactionTypes/transactionHooks.js.map +0 -1
- package/dist/transactionTypes/transactionState.d.ts.map +0 -1
- package/dist/transactionTypes/transactionState.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/utils.helper.d.ts.map +0 -1
- package/dist/utils/utils.helper.js.map +0 -1
|
@@ -3,89 +3,111 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module manager/manager
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { getDefaultContext } from "../context/context.core.js";
|
|
7
|
+
import { internals } from "../transaction/transaction.internal.js";
|
|
8
|
+
import { TransactionRollbackError } from "../transactionErrors/transactionError.types.js";
|
|
9
9
|
import { commitTransaction, rollbackTransaction } from "./manager.commit.js";
|
|
10
|
+
import { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
|
|
11
|
+
import { withRetry } from "./manager.retry.js";
|
|
12
|
+
import { createEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
|
|
10
13
|
/**
|
|
11
14
|
* Create a transaction manager.
|
|
12
15
|
*/
|
|
13
16
|
export function createTransactionManager(options) {
|
|
14
|
-
const { adapter, hooks } = options;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const { adapter, hooks, registry } = options;
|
|
18
|
+
const context = options.context ?? getDefaultContext();
|
|
19
|
+
const emit = createEmitter(options.onEvent);
|
|
20
|
+
/** Arms the timeout, returning a disposer that always clears the timer. */
|
|
21
|
+
function armTimeout(transaction) {
|
|
22
|
+
const timeout = transaction.options.timeout;
|
|
23
|
+
if (!timeout || timeout <= 0)
|
|
24
|
+
return () => { };
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
internals(transaction)._markTimedOut();
|
|
27
|
+
transaction.markRollbackOnly("timeout");
|
|
28
|
+
emit(TRANSACTION_EVENTS.TIMED_OUT, transaction);
|
|
29
|
+
}, timeout);
|
|
30
|
+
return () => clearTimeout(timer);
|
|
31
|
+
}
|
|
21
32
|
return {
|
|
33
|
+
/**
|
|
34
|
+
* Begin a transaction, applying the requested propagation mode.
|
|
35
|
+
*
|
|
36
|
+
* With the default `required` propagation inside an existing transaction
|
|
37
|
+
* this returns a participant: a handle that observes the enclosing
|
|
38
|
+
* transaction but never commits it.
|
|
39
|
+
*/
|
|
22
40
|
async begin(opts) {
|
|
23
|
-
const
|
|
24
|
-
const current =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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());
|
|
41
|
+
const propagation = opts?.propagation ?? "required";
|
|
42
|
+
const current = suspendsTransaction(propagation)
|
|
43
|
+
? undefined
|
|
44
|
+
: context.get();
|
|
45
|
+
const transaction = await resolvePropagation(propagation, {
|
|
46
|
+
current,
|
|
47
|
+
opts,
|
|
48
|
+
adapter,
|
|
49
|
+
hooks,
|
|
50
|
+
emit,
|
|
51
|
+
});
|
|
52
|
+
if (transaction.kind === "root" || transaction.kind === "savepoint") {
|
|
53
|
+
registry?.register(transaction);
|
|
50
54
|
}
|
|
51
55
|
return transaction;
|
|
52
56
|
},
|
|
57
|
+
/**
|
|
58
|
+
* Run a callback inside a transaction, committing or rolling back around it.
|
|
59
|
+
*
|
|
60
|
+
* Only a transaction this call opened is completed here: joining an
|
|
61
|
+
* enclosing transaction must not commit it, and a failure inside a
|
|
62
|
+
* participant marks the enclosing transaction rollback-only instead.
|
|
63
|
+
*/
|
|
53
64
|
async run(callback, opts) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
return withRetry(opts?.retry, async () => {
|
|
66
|
+
const transaction = await this.begin(opts);
|
|
67
|
+
const owned = transaction.kind === "root" || transaction.kind === "savepoint";
|
|
68
|
+
const disposeTimeout = owned ? armTimeout(transaction) : () => { };
|
|
69
|
+
const body = async () => {
|
|
70
|
+
try {
|
|
71
|
+
const result = await callback(transaction);
|
|
60
72
|
await this.commit(transaction);
|
|
73
|
+
return result;
|
|
61
74
|
}
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
if (transaction.state === "active" ||
|
|
66
|
-
transaction.state === "committing") {
|
|
75
|
+
catch (error) {
|
|
67
76
|
try {
|
|
68
77
|
await this.rollback(transaction, error);
|
|
69
78
|
}
|
|
70
79
|
catch (rollbackError) {
|
|
80
|
+
if (rollbackError instanceof TransactionRollbackError)
|
|
81
|
+
throw error;
|
|
71
82
|
throw new TransactionRollbackError(transaction.id, {
|
|
72
83
|
cause: rollbackError,
|
|
73
84
|
originalError: error,
|
|
74
85
|
});
|
|
75
86
|
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
disposeTimeout();
|
|
91
|
+
if (owned)
|
|
92
|
+
registry?.unregister(transaction.id);
|
|
76
93
|
}
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
};
|
|
95
|
+
return transaction.kind === "none"
|
|
96
|
+
? context.exit(body)
|
|
97
|
+
: context.run(transaction, body);
|
|
79
98
|
});
|
|
80
99
|
},
|
|
100
|
+
/** Commit a transaction. Participants and committed transactions are no-ops. */
|
|
81
101
|
async commit(transaction) {
|
|
82
|
-
return commitTransaction(transaction, adapter, hooks);
|
|
102
|
+
return commitTransaction(transaction, adapter, hooks, emit);
|
|
83
103
|
},
|
|
104
|
+
/** Roll back a transaction, or mark the joined transaction rollback-only. */
|
|
84
105
|
async rollback(transaction, reason) {
|
|
85
|
-
return rollbackTransaction(transaction, adapter, reason, hooks);
|
|
106
|
+
return rollbackTransaction(transaction, adapter, reason, hooks, emit);
|
|
86
107
|
},
|
|
108
|
+
/** The transaction in scope for the current async execution, if any. */
|
|
87
109
|
getCurrent() {
|
|
88
|
-
return
|
|
110
|
+
return context.get();
|
|
89
111
|
},
|
|
90
112
|
};
|
|
91
113
|
}
|
|
@@ -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
|
-
*
|
|
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
|
|
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,120 @@
|
|
|
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 {
|
|
11
|
+
import { createNonTransactional, createParticipant, } from "../transaction/transaction.participant.js";
|
|
12
|
+
import { 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
|
+
const parentHandle = internals(parent)._getHandle();
|
|
64
|
+
try {
|
|
65
|
+
await adapter.createSavepoint(parentHandle, savepoint);
|
|
66
|
+
internals(child)._setHandle({ parent: parentHandle, savepoint });
|
|
67
|
+
internals(child)._transition("active");
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
internals(child)._transition("failed");
|
|
71
|
+
emit(TRANSACTION_EVENTS.FAILED, child, error);
|
|
72
|
+
throw new SavepointError(`Failed to create savepoint "${savepoint}"`, error);
|
|
73
|
+
}
|
|
74
|
+
emit(TRANSACTION_EVENTS.STARTED, child);
|
|
75
|
+
if (hooks?.afterBegin)
|
|
76
|
+
await hooks.afterBegin({ transaction: child });
|
|
77
|
+
return child;
|
|
78
|
+
}
|
|
8
79
|
/**
|
|
9
|
-
*
|
|
80
|
+
* Resolve a propagation mode to a transaction handle.
|
|
81
|
+
*
|
|
82
|
+
* @param propagation - The requested mode.
|
|
83
|
+
* @param context - The propagation context.
|
|
84
|
+
* @returns The handle the caller should use.
|
|
85
|
+
* @throws {TransactionPropagationError} when the mode's precondition fails.
|
|
10
86
|
*/
|
|
11
|
-
export async function
|
|
87
|
+
export async function resolvePropagation(propagation, context) {
|
|
88
|
+
const { current, opts } = context;
|
|
12
89
|
switch (propagation) {
|
|
13
90
|
case "required":
|
|
14
|
-
return current;
|
|
15
|
-
case "requires_new":
|
|
16
|
-
|
|
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
|
-
}
|
|
91
|
+
return current ? createParticipant(current) : beginRoot(context);
|
|
92
|
+
case "requires_new":
|
|
93
|
+
return beginRoot(context);
|
|
25
94
|
case "supports":
|
|
26
|
-
return current
|
|
95
|
+
return current
|
|
96
|
+
? createParticipant(current)
|
|
97
|
+
: createNonTransactional(opts);
|
|
27
98
|
case "not_supported":
|
|
28
|
-
return
|
|
99
|
+
return createNonTransactional(opts);
|
|
29
100
|
case "mandatory":
|
|
30
|
-
|
|
101
|
+
if (!current) {
|
|
102
|
+
throw new TransactionPropagationError("Propagation is 'mandatory' but no transaction is in progress");
|
|
103
|
+
}
|
|
104
|
+
return createParticipant(current);
|
|
31
105
|
case "never":
|
|
32
|
-
|
|
106
|
+
if (current) {
|
|
107
|
+
throw new TransactionPropagationError("Transaction exists but propagation is 'never'");
|
|
108
|
+
}
|
|
109
|
+
return createNonTransactional(opts);
|
|
33
110
|
case "nested":
|
|
34
|
-
return
|
|
111
|
+
return current ? beginSavepoint(current, context) : beginRoot(context);
|
|
35
112
|
default:
|
|
36
|
-
throw new TransactionPropagationError(`Unknown propagation: ${propagation}`);
|
|
113
|
+
throw new TransactionPropagationError(`Unknown propagation: ${String(propagation)}`);
|
|
37
114
|
}
|
|
38
115
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
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;
|
|
116
|
+
/** Whether a propagation mode runs its body outside any transaction. */
|
|
117
|
+
export function suspendsTransaction(propagation) {
|
|
118
|
+
return propagation === "not_supported" || propagation === "requires_new";
|
|
55
119
|
}
|
|
56
120
|
//# 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
|
-
*
|
|
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
|
-
*
|
|
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
|