@zudojs/transactions 1.0.0 → 1.1.1
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/README.md +21 -0
- package/dist/manager/manager.commit.js +18 -1
- package/dist/manager/manager.core.d.ts +7 -1
- package/dist/manager/manager.core.js +70 -13
- package/dist/manager/manager.propagation.js +5 -3
- package/dist/transaction/transaction.core.d.ts +3 -1
- package/dist/transaction/transaction.core.js +13 -2
- package/dist/transaction/transaction.internal.d.ts +20 -0
- package/dist/transaction/transaction.internal.js +16 -0
- package/dist/transaction/transaction.participant.js +20 -2
- package/dist/transaction/transaction.savepoint.d.ts +24 -0
- package/dist/transaction/transaction.savepoint.js +29 -0
- package/dist/transactionErrors/index.d.ts +1 -1
- package/dist/transactionErrors/index.js +1 -1
- package/dist/transactionErrors/transactionError.base.d.ts +5 -11
- package/dist/transactionErrors/transactionError.base.js +5 -15
- package/dist/transactionErrors/transactionError.types.d.ts +5 -70
- package/dist/transactionErrors/transactionError.types.js +5 -125
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Transaction lifecycle and coordination with state machine, AsyncLocalStorage context propagation, savepoints, hooks, and adapter abstraction.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-transactions](https://zudojs.oyinlola.site/docs/packages-transactions) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-transactions.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -103,6 +109,21 @@ the transaction.
|
|
|
103
109
|
- Committing a transaction that is not active throws rather than silently
|
|
104
110
|
doing nothing. Only an already-committed transaction is a no-op.
|
|
105
111
|
- A `nested` transaction rolls back to its savepoint, never to the connection.
|
|
112
|
+
Savepoints are always created on the connection, including when the
|
|
113
|
+
enclosing scope is itself a savepoint or a participant.
|
|
114
|
+
- Releasing a savepoint is not a commit. `afterCommit` and `afterRollback`
|
|
115
|
+
callbacks registered inside a `nested` block, and `hooks.afterCommit` for the
|
|
116
|
+
savepoint, move to the enclosing transaction on release. They run only when
|
|
117
|
+
the outermost transaction commits (or, for `afterRollback`, rolls back). A
|
|
118
|
+
savepoint that is itself rolled back runs its `afterRollback` callbacks at
|
|
119
|
+
once and discards its `afterCommit` callbacks.
|
|
120
|
+
- `begin()` and `run()` both honour `timeout`; completing a transaction through
|
|
121
|
+
`manager.commit()` / `manager.rollback()` releases its timer and registry entry.
|
|
122
|
+
- Failures thrown by `afterCommit` callbacks never undo the commit; they are
|
|
123
|
+
reported to `hooks.onError` as an `AggregateError`.
|
|
124
|
+
- `retry` replays only attempts that opened their own transaction. An attempt
|
|
125
|
+
that joined an enclosing transaction has marked it rollback-only and is not
|
|
126
|
+
replayed.
|
|
106
127
|
|
|
107
128
|
## Use Cases
|
|
108
129
|
|
|
@@ -98,6 +98,13 @@ export async function commitTransaction(transaction, adapter, hooks, emit = noop
|
|
|
98
98
|
if (hooks?.beforeCommit)
|
|
99
99
|
await hooks.beforeCommit({ transaction });
|
|
100
100
|
emit(TRANSACTION_EVENTS.COMMITTING, transaction);
|
|
101
|
+
if (hooks?.afterCommit && transaction.kind === "savepoint") {
|
|
102
|
+
// Releasing a savepoint is not a commit. Registered as a callback, the
|
|
103
|
+
// hook is deferred with the savepoint's own callbacks and fires only if
|
|
104
|
+
// the outermost transaction commits.
|
|
105
|
+
const afterCommit = hooks.afterCommit;
|
|
106
|
+
transaction.afterCommit(() => afterCommit({ transaction }));
|
|
107
|
+
}
|
|
101
108
|
try {
|
|
102
109
|
await adapterCommit(transaction, adapter);
|
|
103
110
|
await transaction.commit();
|
|
@@ -110,8 +117,18 @@ export async function commitTransaction(transaction, adapter, hooks, emit = noop
|
|
|
110
117
|
throw new TransactionCommitError(transaction.id, error);
|
|
111
118
|
}
|
|
112
119
|
emit(TRANSACTION_EVENTS.COMMITTED, transaction);
|
|
113
|
-
|
|
120
|
+
// afterCommit callbacks that threw did not undo the commit, but they
|
|
121
|
+
// used to fail silently. Report them without changing the outcome.
|
|
122
|
+
const callbackErrors = internals(transaction)._drainCallbackErrors();
|
|
123
|
+
if (callbackErrors.length > 0 && hooks?.onError) {
|
|
124
|
+
await hooks.onError({
|
|
125
|
+
transaction,
|
|
126
|
+
error: new AggregateError(callbackErrors, "after-commit callback failures"),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (hooks?.afterCommit && transaction.kind !== "savepoint") {
|
|
114
130
|
await hooks.afterCommit({ transaction });
|
|
131
|
+
}
|
|
115
132
|
}
|
|
116
133
|
/**
|
|
117
134
|
* Rollback a transaction with hooks and adapter coordination.
|
|
@@ -41,7 +41,13 @@ export declare function createTransactionManager(options: TransactionManagerOpti
|
|
|
41
41
|
* participant marks the enclosing transaction rollback-only instead.
|
|
42
42
|
*/
|
|
43
43
|
run<T>(callback: (transaction: Transaction) => Promise<T>, opts?: TransactionOptions): Promise<T>;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Commit a transaction. Participants and committed transactions are no-ops.
|
|
46
|
+
*
|
|
47
|
+
* Completing a transaction opened with `begin()` also releases its
|
|
48
|
+
* timeout timer and registry entry; both used to be released only by
|
|
49
|
+
* `run()`, so hand-managed transactions stayed in the registry forever.
|
|
50
|
+
*/
|
|
45
51
|
commit(transaction: Transaction): Promise<void>;
|
|
46
52
|
/** Roll back a transaction, or mark the joined transaction rollback-only. */
|
|
47
53
|
rollback(transaction: Transaction, reason?: unknown): Promise<void>;
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { getDefaultContext } from "../context/context.core.js";
|
|
7
7
|
import { internals } from "../transaction/transaction.internal.js";
|
|
8
|
+
import { isTerminal } from "../transaction/transactionStateMachine.js";
|
|
8
9
|
import { TransactionRollbackError } from "../transactionErrors/transactionError.types.js";
|
|
9
10
|
import { commitTransaction, rollbackTransaction } from "./manager.commit.js";
|
|
10
11
|
import { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
|
|
11
12
|
import { withRetry } from "./manager.retry.js";
|
|
12
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
|
+
}
|
|
13
18
|
/**
|
|
14
19
|
* Create a transaction manager.
|
|
15
20
|
*/
|
|
@@ -17,17 +22,41 @@ export function createTransactionManager(options) {
|
|
|
17
22
|
const { adapter, hooks, registry } = options;
|
|
18
23
|
const context = options.context ?? getDefaultContext();
|
|
19
24
|
const emit = createEmitter(options.onEvent);
|
|
20
|
-
/**
|
|
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
|
+
*/
|
|
21
38
|
function armTimeout(transaction) {
|
|
22
39
|
const timeout = transaction.options.timeout;
|
|
23
40
|
if (!timeout || timeout <= 0)
|
|
24
|
-
return
|
|
41
|
+
return;
|
|
25
42
|
const timer = setTimeout(() => {
|
|
43
|
+
timers.delete(transaction.id);
|
|
26
44
|
internals(transaction)._markTimedOut();
|
|
27
45
|
transaction.markRollbackOnly("timeout");
|
|
28
46
|
emit(TRANSACTION_EVENTS.TIMED_OUT, transaction);
|
|
29
47
|
}, timeout);
|
|
30
|
-
|
|
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);
|
|
31
60
|
}
|
|
32
61
|
return {
|
|
33
62
|
/**
|
|
@@ -49,8 +78,9 @@ export function createTransactionManager(options) {
|
|
|
49
78
|
hooks,
|
|
50
79
|
emit,
|
|
51
80
|
});
|
|
52
|
-
if (transaction
|
|
81
|
+
if (owns(transaction)) {
|
|
53
82
|
registry?.register(transaction);
|
|
83
|
+
armTimeout(transaction);
|
|
54
84
|
}
|
|
55
85
|
return transaction;
|
|
56
86
|
},
|
|
@@ -62,10 +92,21 @@ export function createTransactionManager(options) {
|
|
|
62
92
|
* participant marks the enclosing transaction rollback-only instead.
|
|
63
93
|
*/
|
|
64
94
|
async run(callback, opts) {
|
|
65
|
-
|
|
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 () => {
|
|
66
108
|
const transaction = await this.begin(opts);
|
|
67
|
-
|
|
68
|
-
const disposeTimeout = owned ? armTimeout(transaction) : () => { };
|
|
109
|
+
joined = transaction.kind === "participant";
|
|
69
110
|
const body = async () => {
|
|
70
111
|
try {
|
|
71
112
|
const result = await callback(transaction);
|
|
@@ -87,9 +128,7 @@ export function createTransactionManager(options) {
|
|
|
87
128
|
throw error;
|
|
88
129
|
}
|
|
89
130
|
finally {
|
|
90
|
-
|
|
91
|
-
if (owned)
|
|
92
|
-
registry?.unregister(transaction.id);
|
|
131
|
+
release(transaction);
|
|
93
132
|
}
|
|
94
133
|
};
|
|
95
134
|
return transaction.kind === "none"
|
|
@@ -97,13 +136,31 @@ export function createTransactionManager(options) {
|
|
|
97
136
|
: context.run(transaction, body);
|
|
98
137
|
});
|
|
99
138
|
},
|
|
100
|
-
/**
|
|
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
|
+
*/
|
|
101
146
|
async commit(transaction) {
|
|
102
|
-
|
|
147
|
+
try {
|
|
148
|
+
await commitTransaction(transaction, adapter, hooks, emit);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
if (isFinished(transaction))
|
|
152
|
+
release(transaction);
|
|
153
|
+
}
|
|
103
154
|
},
|
|
104
155
|
/** Roll back a transaction, or mark the joined transaction rollback-only. */
|
|
105
156
|
async rollback(transaction, reason) {
|
|
106
|
-
|
|
157
|
+
try {
|
|
158
|
+
await rollbackTransaction(transaction, adapter, reason, hooks, emit);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
if (isFinished(transaction))
|
|
162
|
+
release(transaction);
|
|
163
|
+
}
|
|
107
164
|
},
|
|
108
165
|
/** The transaction in scope for the current async execution, if any. */
|
|
109
166
|
getCurrent() {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createTransaction } from "../transaction/transaction.core.js";
|
|
11
11
|
import { createNonTransactional, createParticipant, } from "../transaction/transaction.participant.js";
|
|
12
|
-
import { internals } from "../transaction/transaction.internal.js";
|
|
12
|
+
import { connectionHandle, internals, } from "../transaction/transaction.internal.js";
|
|
13
13
|
import { SavepointError, TransactionCapabilityError, TransactionPropagationError, } from "../transactionErrors/transactionError.types.js";
|
|
14
14
|
import { assertAdapterSupports } from "./manager.capabilities.js";
|
|
15
15
|
import { noopEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
|
|
@@ -56,11 +56,13 @@ async function beginSavepoint(parent, context) {
|
|
|
56
56
|
// rule violation, and TransactionCapabilityError names what is missing.
|
|
57
57
|
throw new TransactionCapabilityError("savepoints, required by nested transactions");
|
|
58
58
|
}
|
|
59
|
-
const child = createTransaction(opts, parent.id, "savepoint");
|
|
59
|
+
const child = createTransaction(opts, parent.id, "savepoint", parent);
|
|
60
60
|
if (hooks?.beforeBegin)
|
|
61
61
|
await hooks.beforeBegin({ transaction: child });
|
|
62
62
|
const savepoint = `sp_${child.id}`;
|
|
63
|
-
|
|
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);
|
|
64
66
|
try {
|
|
65
67
|
await adapter.createSavepoint(parentHandle, savepoint);
|
|
66
68
|
internals(child)._setHandle({ parent: parentHandle, savepoint });
|
|
@@ -9,6 +9,8 @@ import type { TransactionKind } from "../transactionTypes/transactionState.js";
|
|
|
9
9
|
* @param options - Options the transaction was started with.
|
|
10
10
|
* @param parentId - Enclosing transaction id, for nested transactions.
|
|
11
11
|
* @param kind - How this handle relates to the adapter transaction.
|
|
12
|
+
* @param parent - The enclosing transaction of a savepoint. Its callbacks
|
|
13
|
+
* are deferred to `parent` on release instead of running.
|
|
12
14
|
*/
|
|
13
|
-
export declare function createTransaction(options?: TransactionOptions, parentId?: string, kind?: TransactionKind): Transaction;
|
|
15
|
+
export declare function createTransaction(options?: TransactionOptions, parentId?: string, kind?: TransactionKind, parent?: Transaction): Transaction;
|
|
14
16
|
//# sourceMappingURL=transaction.core.d.ts.map
|
|
@@ -5,6 +5,7 @@ import { randomBytes } from "node:crypto";
|
|
|
5
5
|
import { TransactionRollbackError, TransactionStateError, } from "../transactionErrors/transactionError.types.js";
|
|
6
6
|
import { canTransition, createTransitionFunction, } from "./transactionStateMachine.js";
|
|
7
7
|
import { attachInternals } from "./transaction.internal.js";
|
|
8
|
+
import { deferCallbacksToParent } from "./transaction.savepoint.js";
|
|
8
9
|
/**
|
|
9
10
|
* Generate a unique transaction ID.
|
|
10
11
|
*/
|
|
@@ -30,14 +31,17 @@ async function runCallbacks(callbacks) {
|
|
|
30
31
|
* @param options - Options the transaction was started with.
|
|
31
32
|
* @param parentId - Enclosing transaction id, for nested transactions.
|
|
32
33
|
* @param kind - How this handle relates to the adapter transaction.
|
|
34
|
+
* @param parent - The enclosing transaction of a savepoint. Its callbacks
|
|
35
|
+
* are deferred to `parent` on release instead of running.
|
|
33
36
|
*/
|
|
34
|
-
export function createTransaction(options = {}, parentId, kind = "root") {
|
|
37
|
+
export function createTransaction(options = {}, parentId, kind = "root", parent) {
|
|
35
38
|
let state = "pending";
|
|
36
39
|
let rollbackOnly = false;
|
|
37
40
|
let rollbackOnlyReason;
|
|
38
41
|
let timedOut = false;
|
|
39
42
|
const afterCommitCallbacks = [];
|
|
40
43
|
const afterRollbackCallbacks = [];
|
|
44
|
+
let callbackErrors = [];
|
|
41
45
|
let handle;
|
|
42
46
|
const transition = createTransitionFunction(() => state, (next) => {
|
|
43
47
|
state = next;
|
|
@@ -90,8 +94,14 @@ export function createTransaction(options = {}, parentId, kind = "root") {
|
|
|
90
94
|
}
|
|
91
95
|
transition("committing");
|
|
92
96
|
transition("committed");
|
|
97
|
+
if (kind === "savepoint" && parent !== undefined) {
|
|
98
|
+
deferCallbacksToParent(parent, afterCommitCallbacks, afterRollbackCallbacks);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
93
101
|
afterRollbackCallbacks.length = 0;
|
|
94
|
-
|
|
102
|
+
// The commit stands whatever the callbacks do; their failures are
|
|
103
|
+
// kept for the manager to report instead of being dropped.
|
|
104
|
+
callbackErrors = await runCallbacks(afterCommitCallbacks);
|
|
95
105
|
},
|
|
96
106
|
async rollback(reason) {
|
|
97
107
|
if (state === "rolled_back" || state === "failed")
|
|
@@ -137,6 +147,7 @@ export function createTransaction(options = {}, parentId, kind = "root") {
|
|
|
137
147
|
timedOut = true;
|
|
138
148
|
},
|
|
139
149
|
_getRollbackOnlyReason: () => rollbackOnlyReason,
|
|
150
|
+
_drainCallbackErrors: () => callbackErrors.splice(0),
|
|
140
151
|
});
|
|
141
152
|
}
|
|
142
153
|
//# sourceMappingURL=transaction.core.js.map
|
|
@@ -31,6 +31,14 @@ export interface TransactionInternals {
|
|
|
31
31
|
_markTimedOut(): void;
|
|
32
32
|
/** The reason supplied to `markRollbackOnly`, if any. */
|
|
33
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[];
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Access the manager-only surface of a transaction.
|
|
@@ -55,6 +63,18 @@ export interface SavepointHandle {
|
|
|
55
63
|
/** The savepoint name created on that handle. */
|
|
56
64
|
readonly savepoint: string;
|
|
57
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;
|
|
58
78
|
/**
|
|
59
79
|
* Narrow an adapter handle to a savepoint handle.
|
|
60
80
|
*
|
|
@@ -48,6 +48,22 @@ export function attachInternals(transaction, operations) {
|
|
|
48
48
|
});
|
|
49
49
|
return transaction;
|
|
50
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
|
+
}
|
|
51
67
|
/**
|
|
52
68
|
* Narrow an adapter handle to a savepoint handle.
|
|
53
69
|
*
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module transaction/transaction.participant
|
|
11
11
|
*/
|
|
12
12
|
import { createTransaction } from "./transaction.core.js";
|
|
13
|
-
import { internals } from "./transaction.internal.js";
|
|
13
|
+
import { attachInternals, internals } from "./transaction.internal.js";
|
|
14
14
|
/**
|
|
15
15
|
* Create a handle that joins an in-progress transaction.
|
|
16
16
|
*
|
|
@@ -18,7 +18,7 @@ import { internals } from "./transaction.internal.js";
|
|
|
18
18
|
* @returns A participant handle delegating to `parent`.
|
|
19
19
|
*/
|
|
20
20
|
export function createParticipant(parent) {
|
|
21
|
-
|
|
21
|
+
const participant = {
|
|
22
22
|
get id() {
|
|
23
23
|
return parent.id;
|
|
24
24
|
},
|
|
@@ -61,7 +61,25 @@ export function createParticipant(parent) {
|
|
|
61
61
|
afterRollback(callback) {
|
|
62
62
|
parent.afterRollback(callback);
|
|
63
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: () => [],
|
|
64
81
|
});
|
|
82
|
+
return Object.freeze(participant);
|
|
65
83
|
}
|
|
66
84
|
/**
|
|
67
85
|
* Create a handle for a deliberately non-transactional scope.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Savepoint callback deferral.
|
|
3
|
+
*
|
|
4
|
+
* Releasing a savepoint is not a commit: the enclosing transaction can still
|
|
5
|
+
* roll back and discard everything the savepoint did. Callbacks registered on
|
|
6
|
+
* a savepoint therefore move to its parent when the savepoint is released,
|
|
7
|
+
* the same way a participant registers straight on the transaction it
|
|
8
|
+
* joined. They run once the outermost transaction settles: `afterCommit`
|
|
9
|
+
* when it commits, `afterRollback` when it rolls back.
|
|
10
|
+
*
|
|
11
|
+
* @module transaction/transaction.savepoint
|
|
12
|
+
*/
|
|
13
|
+
import type { Transaction } from "../transactionTypes/transaction.interface.js";
|
|
14
|
+
/**
|
|
15
|
+
* Move a released savepoint's pending callbacks onto its parent.
|
|
16
|
+
*
|
|
17
|
+
* Both arrays are emptied: the callbacks now belong to the parent.
|
|
18
|
+
*
|
|
19
|
+
* @param parent - The transaction the savepoint was created on.
|
|
20
|
+
* @param afterCommit - The savepoint's pending after-commit callbacks.
|
|
21
|
+
* @param afterRollback - The savepoint's pending after-rollback callbacks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deferCallbacksToParent(parent: Transaction, afterCommit: Array<() => Promise<void>>, afterRollback: Array<() => Promise<void>>): void;
|
|
24
|
+
//# sourceMappingURL=transaction.savepoint.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Savepoint callback deferral.
|
|
3
|
+
*
|
|
4
|
+
* Releasing a savepoint is not a commit: the enclosing transaction can still
|
|
5
|
+
* roll back and discard everything the savepoint did. Callbacks registered on
|
|
6
|
+
* a savepoint therefore move to its parent when the savepoint is released,
|
|
7
|
+
* the same way a participant registers straight on the transaction it
|
|
8
|
+
* joined. They run once the outermost transaction settles: `afterCommit`
|
|
9
|
+
* when it commits, `afterRollback` when it rolls back.
|
|
10
|
+
*
|
|
11
|
+
* @module transaction/transaction.savepoint
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Move a released savepoint's pending callbacks onto its parent.
|
|
15
|
+
*
|
|
16
|
+
* Both arrays are emptied: the callbacks now belong to the parent.
|
|
17
|
+
*
|
|
18
|
+
* @param parent - The transaction the savepoint was created on.
|
|
19
|
+
* @param afterCommit - The savepoint's pending after-commit callbacks.
|
|
20
|
+
* @param afterRollback - The savepoint's pending after-rollback callbacks.
|
|
21
|
+
*/
|
|
22
|
+
export function deferCallbacksToParent(parent, afterCommit, afterRollback) {
|
|
23
|
+
for (const callback of afterCommit.splice(0))
|
|
24
|
+
parent.afterCommit(callback);
|
|
25
|
+
for (const callback of afterRollback.splice(0)) {
|
|
26
|
+
parent.afterRollback(callback);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=transaction.savepoint.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transaction error types.
|
|
3
3
|
*/
|
|
4
|
-
export { TransactionError } from "./transactionError.base.js";
|
|
4
|
+
export { TransactionError, type TransactionErrorOptions, } from "./transactionError.base.js";
|
|
5
5
|
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transaction error types.
|
|
3
3
|
*/
|
|
4
|
-
export { TransactionError } from "./transactionError.base.js";
|
|
4
|
+
export { TransactionError, } from "./transactionError.base.js";
|
|
5
5
|
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base transaction error class.
|
|
3
|
+
*
|
|
4
|
+
* Owned by `@zudojs/errors` (round 10 INF-16) and re-exported here, so
|
|
5
|
+
* `instanceof TransactionError` matches whichever package a caller imports
|
|
6
|
+
* it from. Name, constructor, `code` defaults and category are unchanged.
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base error for all transaction-related failures.
|
|
7
|
-
*/
|
|
8
|
-
export declare class TransactionError extends BaseError {
|
|
9
|
-
constructor(message: string, options?: {
|
|
10
|
-
readonly code?: ErrorCode;
|
|
11
|
-
readonly metadata?: ErrorMetadata;
|
|
12
|
-
readonly cause?: unknown;
|
|
13
|
-
});
|
|
14
|
-
}
|
|
8
|
+
export { TransactionError, type TransactionErrorOptions } from "@zudojs/errors";
|
|
15
9
|
//# sourceMappingURL=transactionError.base.d.ts.map
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base transaction error class.
|
|
3
|
+
*
|
|
4
|
+
* Owned by `@zudojs/errors` (round 10 INF-16) and re-exported here, so
|
|
5
|
+
* `instanceof TransactionError` matches whichever package a caller imports
|
|
6
|
+
* it from. Name, constructor, `code` defaults and category are unchanged.
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base error for all transaction-related failures.
|
|
7
|
-
*/
|
|
8
|
-
export class TransactionError extends BaseError {
|
|
9
|
-
constructor(message, options) {
|
|
10
|
-
super(message, {
|
|
11
|
-
code: options?.code ?? ErrorCode.OPERATION_FAILED,
|
|
12
|
-
category: ErrorCategory.DATABASE,
|
|
13
|
-
severity: ErrorSeverity.ERROR,
|
|
14
|
-
metadata: options?.metadata,
|
|
15
|
-
cause: options?.cause,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
8
|
+
export { TransactionError } from "@zudojs/errors";
|
|
19
9
|
//# sourceMappingURL=transactionError.base.js.map
|
|
@@ -1,74 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Specific transaction error subclasses.
|
|
3
|
+
*
|
|
4
|
+
* Owned by `@zudojs/errors` (round 10 INF-16) and re-exported here with the
|
|
5
|
+
* same names, constructor signatures and codes, so `instanceof` checks match
|
|
6
|
+
* across both import paths.
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Transaction is in an invalid state for the requested operation.
|
|
7
|
-
*/
|
|
8
|
-
export declare class TransactionStateError extends TransactionError {
|
|
9
|
-
constructor(state: string, operation: string);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Transaction exceeded its timeout.
|
|
13
|
-
*/
|
|
14
|
-
export declare class TransactionTimeoutError extends TransactionError {
|
|
15
|
-
constructor(transactionId: string, timeoutMs: number);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Transaction commit failed.
|
|
19
|
-
*/
|
|
20
|
-
export declare class TransactionCommitError extends TransactionError {
|
|
21
|
-
constructor(transactionId: string, cause?: unknown);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Transaction rollback failed.
|
|
25
|
-
*/
|
|
26
|
-
export declare class TransactionRollbackError extends TransactionError {
|
|
27
|
-
constructor(transactionId: string, options?: {
|
|
28
|
-
readonly cause?: unknown;
|
|
29
|
-
readonly originalError?: unknown;
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The underlying adapter threw an error.
|
|
34
|
-
*/
|
|
35
|
-
export declare class TransactionAdapterError extends TransactionError {
|
|
36
|
-
constructor(message: string, cause?: unknown);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Propagation strategy violation.
|
|
40
|
-
*/
|
|
41
|
-
export declare class TransactionPropagationError extends TransactionError {
|
|
42
|
-
constructor(message: string);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Required isolation level is not supported by the adapter.
|
|
46
|
-
*/
|
|
47
|
-
export declare class TransactionIsolationError extends TransactionError {
|
|
48
|
-
constructor(level: string);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Savepoint operation failed.
|
|
52
|
-
*/
|
|
53
|
-
export declare class SavepointError extends TransactionError {
|
|
54
|
-
constructor(message: string, cause?: unknown);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* A transaction is required but none exists.
|
|
58
|
-
*/
|
|
59
|
-
export declare class TransactionRequiredError extends TransactionError {
|
|
60
|
-
constructor();
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* A transaction exists but none was expected.
|
|
64
|
-
*/
|
|
65
|
-
export declare class TransactionUnexpectedError extends TransactionError {
|
|
66
|
-
constructor();
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The adapter does not support the requested capability.
|
|
70
|
-
*/
|
|
71
|
-
export declare class TransactionCapabilityError extends TransactionError {
|
|
72
|
-
constructor(capability: string);
|
|
73
|
-
}
|
|
8
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
74
9
|
//# sourceMappingURL=transactionError.types.d.ts.map
|
|
@@ -1,129 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Specific transaction error subclasses.
|
|
3
|
+
*
|
|
4
|
+
* Owned by `@zudojs/errors` (round 10 INF-16) and re-exported here with the
|
|
5
|
+
* same names, constructor signatures and codes, so `instanceof` checks match
|
|
6
|
+
* across both import paths.
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
import { TransactionError } from "./transactionError.base.js";
|
|
6
|
-
/**
|
|
7
|
-
* Transaction is in an invalid state for the requested operation.
|
|
8
|
-
*/
|
|
9
|
-
export class TransactionStateError extends TransactionError {
|
|
10
|
-
constructor(state, operation) {
|
|
11
|
-
super(`Cannot ${operation} transaction in state "${state}"`, {
|
|
12
|
-
code: ErrorCode.LIFECYCLE_STATE,
|
|
13
|
-
metadata: { state, operation },
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Transaction exceeded its timeout.
|
|
19
|
-
*/
|
|
20
|
-
export class TransactionTimeoutError extends TransactionError {
|
|
21
|
-
constructor(transactionId, timeoutMs) {
|
|
22
|
-
super(`Transaction "${transactionId}" timed out after ${timeoutMs}ms`, {
|
|
23
|
-
code: ErrorCode.TIMEOUT,
|
|
24
|
-
metadata: { transactionId, timeoutMs },
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Transaction commit failed.
|
|
30
|
-
*/
|
|
31
|
-
export class TransactionCommitError extends TransactionError {
|
|
32
|
-
constructor(transactionId, cause) {
|
|
33
|
-
super(`Transaction "${transactionId}" commit failed`, {
|
|
34
|
-
code: ErrorCode.DATABASE_TRANSACTION,
|
|
35
|
-
cause,
|
|
36
|
-
metadata: { transactionId },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Transaction rollback failed.
|
|
42
|
-
*/
|
|
43
|
-
export class TransactionRollbackError extends TransactionError {
|
|
44
|
-
constructor(transactionId, options) {
|
|
45
|
-
super(`Transaction "${transactionId}" rollback failed`, {
|
|
46
|
-
code: ErrorCode.DATABASE_TRANSACTION,
|
|
47
|
-
cause: options?.cause,
|
|
48
|
-
metadata: {
|
|
49
|
-
transactionId,
|
|
50
|
-
originalError: options?.originalError instanceof Error
|
|
51
|
-
? options.originalError.message
|
|
52
|
-
: String(options?.originalError ?? "unknown"),
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* The underlying adapter threw an error.
|
|
59
|
-
*/
|
|
60
|
-
export class TransactionAdapterError extends TransactionError {
|
|
61
|
-
constructor(message, cause) {
|
|
62
|
-
super(message, {
|
|
63
|
-
code: ErrorCode.ADAPTER_OPERATION_FAILED,
|
|
64
|
-
cause,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Propagation strategy violation.
|
|
70
|
-
*/
|
|
71
|
-
export class TransactionPropagationError extends TransactionError {
|
|
72
|
-
constructor(message) {
|
|
73
|
-
super(message, { code: ErrorCode.VALIDATION_FAILED });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Required isolation level is not supported by the adapter.
|
|
78
|
-
*/
|
|
79
|
-
export class TransactionIsolationError extends TransactionError {
|
|
80
|
-
constructor(level) {
|
|
81
|
-
super(`Isolation level "${level}" is not supported by the adapter`, {
|
|
82
|
-
code: ErrorCode.VALIDATION_FAILED,
|
|
83
|
-
metadata: { level },
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Savepoint operation failed.
|
|
89
|
-
*/
|
|
90
|
-
export class SavepointError extends TransactionError {
|
|
91
|
-
constructor(message, cause) {
|
|
92
|
-
super(message, {
|
|
93
|
-
code: ErrorCode.OPERATION_FAILED,
|
|
94
|
-
cause,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* A transaction is required but none exists.
|
|
100
|
-
*/
|
|
101
|
-
export class TransactionRequiredError extends TransactionError {
|
|
102
|
-
constructor() {
|
|
103
|
-
super("A transaction is required but none exists", {
|
|
104
|
-
code: ErrorCode.PRECONDITION_FAILED,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* A transaction exists but none was expected.
|
|
110
|
-
*/
|
|
111
|
-
export class TransactionUnexpectedError extends TransactionError {
|
|
112
|
-
constructor() {
|
|
113
|
-
super("A transaction already exists but none was expected", {
|
|
114
|
-
code: ErrorCode.CONFLICT,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* The adapter does not support the requested capability.
|
|
120
|
-
*/
|
|
121
|
-
export class TransactionCapabilityError extends TransactionError {
|
|
122
|
-
constructor(capability) {
|
|
123
|
-
super(`Adapter does not support: ${capability}`, {
|
|
124
|
-
code: ErrorCode.NOT_IMPLEMENTED,
|
|
125
|
-
metadata: { capability },
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
8
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
129
9
|
//# sourceMappingURL=transactionError.types.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/transactions",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Transaction lifecycle and coordination with state machine, AsyncLocalStorage context propagation, savepoints, hooks, and adapter abstraction.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Oluwayemi Oyinlola",
|
|
8
|
+
"url": "https://github.com/oyinlola-tech"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"main": "./dist/index.js",
|
|
8
12
|
"module": "./dist/index.js",
|
|
@@ -21,7 +25,7 @@
|
|
|
21
25
|
"!dist/.tsbuildinfo"
|
|
22
26
|
],
|
|
23
27
|
"dependencies": {
|
|
24
|
-
"@zudojs/errors": "1.
|
|
28
|
+
"@zudojs/errors": "1.1.0"
|
|
25
29
|
},
|
|
26
30
|
"engines": {
|
|
27
31
|
"node": ">=24.0.0"
|