@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.
- package/LICENSE +21 -0
- package/README.md +91 -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 +121 -24
- package/dist/manager/manager.core.d.ts +34 -3
- package/dist/manager/manager.core.js +132 -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 +102 -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 +64 -57
- package/dist/transaction/transaction.internal.d.ts +85 -0
- package/dist/transaction/transaction.internal.js +82 -0
- package/dist/transaction/transaction.participant.d.ts +31 -0
- package/dist/transaction/transaction.participant.js +99 -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 +24 -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
|
@@ -2,33 +2,49 @@
|
|
|
2
2
|
* Core Transaction implementation with state machine enforcement.
|
|
3
3
|
*/
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
|
-
import {
|
|
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;
|
|
20
38
|
let timedOut = false;
|
|
21
39
|
const afterCommitCallbacks = [];
|
|
22
40
|
const afterRollbackCallbacks = [];
|
|
41
|
+
let callbackErrors = [];
|
|
23
42
|
let handle;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
const getHandle = () => handle;
|
|
28
|
-
const transition = createTransitionFunction(() => state, (s) => {
|
|
29
|
-
state = s;
|
|
43
|
+
const transition = createTransitionFunction(() => state, (next) => {
|
|
44
|
+
state = next;
|
|
30
45
|
});
|
|
31
46
|
const metadata = new Map(options.metadata ? Object.entries(options.metadata) : []);
|
|
47
|
+
const frozenOptions = Object.freeze({ ...options });
|
|
32
48
|
const id = generateTransactionId();
|
|
33
49
|
const startedAt = Date.now();
|
|
34
50
|
const txn = {
|
|
@@ -38,75 +54,64 @@ export function createTransaction(options = {}, parentId) {
|
|
|
38
54
|
get parentId() {
|
|
39
55
|
return parentId;
|
|
40
56
|
},
|
|
57
|
+
get kind() {
|
|
58
|
+
return kind;
|
|
59
|
+
},
|
|
41
60
|
get state() {
|
|
42
61
|
return state;
|
|
43
62
|
},
|
|
44
63
|
get options() {
|
|
45
|
-
return
|
|
64
|
+
return frozenOptions;
|
|
46
65
|
},
|
|
47
66
|
get startedAt() {
|
|
48
67
|
return startedAt;
|
|
49
68
|
},
|
|
50
69
|
get metadata() {
|
|
51
|
-
return metadata;
|
|
70
|
+
return new Map(metadata);
|
|
52
71
|
},
|
|
53
72
|
get timedOut() {
|
|
54
73
|
return timedOut;
|
|
55
74
|
},
|
|
75
|
+
/**
|
|
76
|
+
* Mark the transaction committed and run its after-commit callbacks.
|
|
77
|
+
*
|
|
78
|
+
* Refuses outright when the transaction is rollback-only: a caller must
|
|
79
|
+
* never be able to mistake a rollback for a commit.
|
|
80
|
+
*/
|
|
56
81
|
async commit() {
|
|
82
|
+
if (state === "committed")
|
|
83
|
+
return;
|
|
57
84
|
if (state !== "active") {
|
|
58
85
|
throw new TransactionStateError(state, "commit");
|
|
59
86
|
}
|
|
60
87
|
if (rollbackOnly) {
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
throw new TransactionRollbackError(id, {
|
|
89
|
+
originalError: rollbackOnlyReason ?? "marked rollback-only",
|
|
90
|
+
});
|
|
63
91
|
}
|
|
64
92
|
transition("committing");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
}
|
|
93
|
+
transition("committed");
|
|
94
|
+
afterRollbackCallbacks.length = 0;
|
|
95
|
+
// The commit stands whatever the callbacks do; their failures are
|
|
96
|
+
// kept for the manager to report instead of being dropped.
|
|
97
|
+
callbackErrors = await runCallbacks(afterCommitCallbacks);
|
|
82
98
|
},
|
|
83
99
|
async rollback(reason) {
|
|
84
|
-
if (state === "
|
|
85
|
-
state === "rolled_back" ||
|
|
86
|
-
state === "failed") {
|
|
100
|
+
if (state === "rolled_back" || state === "failed")
|
|
87
101
|
return;
|
|
102
|
+
if (state === "committed") {
|
|
103
|
+
throw new TransactionStateError(state, "rollback");
|
|
88
104
|
}
|
|
89
|
-
if (state
|
|
105
|
+
if (!canTransition(state, "rolling_back")) {
|
|
90
106
|
throw new TransactionStateError(state, "rollback");
|
|
91
107
|
}
|
|
92
108
|
transition("rolling_back");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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");
|
|
109
|
+
transition("rolled_back");
|
|
110
|
+
afterCommitCallbacks.length = 0;
|
|
111
|
+
const errors = await runCallbacks(afterRollbackCallbacks);
|
|
112
|
+
if (errors.length > 0) {
|
|
108
113
|
throw new TransactionRollbackError(id, {
|
|
109
|
-
cause:
|
|
114
|
+
cause: new AggregateError(errors, "after-rollback callback failures"),
|
|
110
115
|
originalError: reason,
|
|
111
116
|
});
|
|
112
117
|
}
|
|
@@ -125,15 +130,17 @@ export function createTransaction(options = {}, parentId) {
|
|
|
125
130
|
afterRollbackCallbacks.push(callback);
|
|
126
131
|
},
|
|
127
132
|
};
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
return attachInternals(txn, {
|
|
134
|
+
_setHandle: (next) => {
|
|
135
|
+
handle = next;
|
|
136
|
+
},
|
|
137
|
+
_getHandle: () => handle,
|
|
138
|
+
_transition: transition,
|
|
139
|
+
_markTimedOut: () => {
|
|
133
140
|
timedOut = true;
|
|
134
141
|
},
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
_getRollbackOnlyReason: () => rollbackOnlyReason,
|
|
143
|
+
_drainCallbackErrors: () => callbackErrors.splice(0),
|
|
137
144
|
});
|
|
138
145
|
}
|
|
139
146
|
//# sourceMappingURL=transaction.core.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
* Take the failures collected from `afterCommit` callbacks.
|
|
36
|
+
*
|
|
37
|
+
* The transaction stays committed when a callback throws — nothing can
|
|
38
|
+
* undo the adapter commit — but the failures used to vanish without a
|
|
39
|
+
* trace. The manager drains them and reports them to `hooks.onError`.
|
|
40
|
+
*/
|
|
41
|
+
_drainCallbackErrors(): unknown[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Access the manager-only surface of a transaction.
|
|
45
|
+
*
|
|
46
|
+
* @param transaction - A transaction created by `createTransaction`.
|
|
47
|
+
* @returns The internal operations.
|
|
48
|
+
* @throws {TypeError} when the transaction did not come from this package.
|
|
49
|
+
*/
|
|
50
|
+
export declare function internals(transaction: Transaction): TransactionInternals;
|
|
51
|
+
/**
|
|
52
|
+
* Attach the manager-only surface to a freshly created transaction.
|
|
53
|
+
*
|
|
54
|
+
* @param transaction - The transaction to extend.
|
|
55
|
+
* @param operations - The internal operations to attach.
|
|
56
|
+
* @returns The same transaction.
|
|
57
|
+
*/
|
|
58
|
+
export declare function attachInternals<T extends Transaction>(transaction: T, operations: TransactionInternals): T;
|
|
59
|
+
/** A savepoint handle stored on a nested transaction. */
|
|
60
|
+
export interface SavepointHandle {
|
|
61
|
+
/** The enclosing transaction's adapter handle. */
|
|
62
|
+
readonly parent: unknown;
|
|
63
|
+
/** The savepoint name created on that handle. */
|
|
64
|
+
readonly savepoint: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the adapter connection a transaction runs on.
|
|
68
|
+
*
|
|
69
|
+
* A savepoint's handle names its parent connection; a participant's handle
|
|
70
|
+
* is the joined transaction's. A savepoint opened inside another savepoint
|
|
71
|
+
* used to be created against the outer *savepoint handle* rather than the
|
|
72
|
+
* connection, which no adapter can act on.
|
|
73
|
+
*
|
|
74
|
+
* @param transaction - Any transaction created by this package.
|
|
75
|
+
* @returns The connection-level adapter handle.
|
|
76
|
+
*/
|
|
77
|
+
export declare function connectionHandle(transaction: Transaction): unknown;
|
|
78
|
+
/**
|
|
79
|
+
* Narrow an adapter handle to a savepoint handle.
|
|
80
|
+
*
|
|
81
|
+
* @param handle - The handle stored on a transaction.
|
|
82
|
+
* @returns The savepoint handle, or undefined when it is a plain handle.
|
|
83
|
+
*/
|
|
84
|
+
export declare function asSavepointHandle(handle: unknown): SavepointHandle | undefined;
|
|
85
|
+
//# sourceMappingURL=transaction.internal.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
* Resolve the adapter connection a transaction runs on.
|
|
53
|
+
*
|
|
54
|
+
* A savepoint's handle names its parent connection; a participant's handle
|
|
55
|
+
* is the joined transaction's. A savepoint opened inside another savepoint
|
|
56
|
+
* used to be created against the outer *savepoint handle* rather than the
|
|
57
|
+
* connection, which no adapter can act on.
|
|
58
|
+
*
|
|
59
|
+
* @param transaction - Any transaction created by this package.
|
|
60
|
+
* @returns The connection-level adapter handle.
|
|
61
|
+
*/
|
|
62
|
+
export function connectionHandle(transaction) {
|
|
63
|
+
const handle = internals(transaction)._getHandle();
|
|
64
|
+
const savepoint = asSavepointHandle(handle);
|
|
65
|
+
return savepoint ? savepoint.parent : handle;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Narrow an adapter handle to a savepoint handle.
|
|
69
|
+
*
|
|
70
|
+
* @param handle - The handle stored on a transaction.
|
|
71
|
+
* @returns The savepoint handle, or undefined when it is a plain handle.
|
|
72
|
+
*/
|
|
73
|
+
export function asSavepointHandle(handle) {
|
|
74
|
+
if (typeof handle === "object" &&
|
|
75
|
+
handle !== null &&
|
|
76
|
+
"savepoint" in handle &&
|
|
77
|
+
typeof handle.savepoint === "string") {
|
|
78
|
+
return handle;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
//# 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,99 @@
|
|
|
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 { attachInternals, 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
|
+
const participant = {
|
|
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
|
+
// A participant owns nothing, but the manager still has to reach the
|
|
66
|
+
// adapter handle of the transaction it joined: a `nested` run inside a
|
|
67
|
+
// participant scope used to throw "Transaction was not created by
|
|
68
|
+
// @zudojs/transactions" because the frozen participant carried no
|
|
69
|
+
// internals at all. Reads delegate to the joined transaction; writes
|
|
70
|
+
// are refused, since only the owner may drive its state.
|
|
71
|
+
const refuse = () => {
|
|
72
|
+
throw new TypeError("A participant does not own the transaction it joined and cannot modify it.");
|
|
73
|
+
};
|
|
74
|
+
attachInternals(participant, {
|
|
75
|
+
_setHandle: refuse,
|
|
76
|
+
_getHandle: () => internals(parent)._getHandle(),
|
|
77
|
+
_transition: refuse,
|
|
78
|
+
_markTimedOut: refuse,
|
|
79
|
+
_getRollbackOnlyReason: () => internals(parent)._getRollbackOnlyReason(),
|
|
80
|
+
_drainCallbackErrors: () => [],
|
|
81
|
+
});
|
|
82
|
+
return Object.freeze(participant);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a handle for a deliberately non-transactional scope.
|
|
86
|
+
*
|
|
87
|
+
* Returned by `supports` and `never` when nothing is in progress, and by
|
|
88
|
+
* `not_supported` always. It moves through the normal lifecycle so callers can
|
|
89
|
+
* treat it uniformly, but never touches an adapter.
|
|
90
|
+
*
|
|
91
|
+
* @param options - Options the scope was opened with.
|
|
92
|
+
* @returns A transaction handle bound to no adapter transaction.
|
|
93
|
+
*/
|
|
94
|
+
export function createNonTransactional(options = {}) {
|
|
95
|
+
const txn = createTransaction(options, undefined, "none");
|
|
96
|
+
internals(txn)._transition("active");
|
|
97
|
+
return txn;
|
|
98
|
+
}
|
|
99
|
+
//# 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 (!
|
|
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
|
|
9
|
-
export { type TransactionHookContext, type TransactionErrorContext, type TransactionHooks, type TransactionRegistry,
|
|
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
|