@zudojs/transactions 1.1.2 → 1.2.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 +46 -1
- package/dist/context/context.handle.d.ts +36 -0
- package/dist/context/context.handle.js +41 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/manager/manager.commit.d.ts +9 -1
- package/dist/manager/manager.commit.js +17 -11
- package/dist/manager/manager.core.d.ts +12 -0
- package/dist/manager/manager.core.js +20 -1
- package/dist/transaction/transaction.core.js +10 -4
- package/dist/transaction/transaction.participant.js +3 -0
- package/dist/transactionErrors/index.d.ts +1 -1
- package/dist/transactionErrors/index.js +1 -1
- package/dist/transactionErrors/transactionError.types.d.ts +1 -1
- package/dist/transactionErrors/transactionError.types.js +1 -1
- package/dist/transactionTypes/transaction.interface.d.ts +15 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/utils.signal.d.ts +15 -0
- package/dist/utils/utils.signal.js +34 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -47,6 +47,47 @@ await manager.run(handler, {
|
|
|
47
47
|
});
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
## Reaching the adapter handle
|
|
51
|
+
|
|
52
|
+
Whatever your adapter's `begin()` returned (typically a database client or
|
|
53
|
+
connection bound to the transaction) is available inside the transaction:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { currentTransactionHandle, getTransactionHandle } from "@zudojs/transactions";
|
|
57
|
+
|
|
58
|
+
await manager.run(async (transaction) => {
|
|
59
|
+
const tx = getTransactionHandle<PoolClient>(transaction);
|
|
60
|
+
// or, anywhere below this call in the same async flow:
|
|
61
|
+
const same = manager.getCurrentHandle<PoolClient>();
|
|
62
|
+
const alsoSame = currentTransactionHandle<PoolClient>(); // default context only
|
|
63
|
+
await tx!.query("INSERT ...");
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A savepoint resolves to its connection's handle and a participant to the
|
|
68
|
+
joined transaction's. Outside a transaction, or in a non-transactional scope
|
|
69
|
+
(`supports`/`not_supported`/`never` with nothing to join), the result is
|
|
70
|
+
`undefined`. Use the handle to issue work; commit and roll back through the
|
|
71
|
+
manager, not the handle.
|
|
72
|
+
|
|
73
|
+
## Timeouts
|
|
74
|
+
|
|
75
|
+
With `timeout`, the transaction's `signal` aborts when it runs out, with a
|
|
76
|
+
`TransactionTimeoutError` as `signal.reason`. `run()` then stops waiting for
|
|
77
|
+
the callback, rolls back, and rejects with that error. JavaScript cannot stop
|
|
78
|
+
the callback itself, so pass the signal to anything cancellable:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
await manager.run(
|
|
82
|
+
async (transaction) => {
|
|
83
|
+
await fetch(url, { signal: transaction.signal });
|
|
84
|
+
},
|
|
85
|
+
{ timeout: 5_000 },
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`timed_out` is emitted once, when the timeout fires.
|
|
90
|
+
|
|
50
91
|
## Propagation
|
|
51
92
|
|
|
52
93
|
| Mode | Transaction in progress | None in progress |
|
|
@@ -81,7 +122,8 @@ the transaction.
|
|
|
81
122
|
| Condition | Error |
|
|
82
123
|
| -------------------------------------- | ------------------------------ |
|
|
83
124
|
| Transaction outlived its `timeout` | `TransactionTimeoutError` |
|
|
84
|
-
| Marked rollback-only, then committed | `TransactionRollbackError`
|
|
125
|
+
| Marked rollback-only, then committed | `TransactionRollbackOnlyError` (a `TransactionRollbackError`): "commit refused: transaction marked rollback-only" |
|
|
126
|
+
| Rolling back a committed transaction | `TransactionStateError` |
|
|
85
127
|
| Adapter lacks the requested isolation | `TransactionIsolationError` |
|
|
86
128
|
| Adapter lacks another requested feature | `TransactionCapabilityError` |
|
|
87
129
|
| Savepoint create/rollback/release fails | `SavepointError` |
|
|
@@ -108,6 +150,9 @@ the transaction.
|
|
|
108
150
|
back and `commit()` rejects. A rollback can never be reported as a commit.
|
|
109
151
|
- Committing a transaction that is not active throws rather than silently
|
|
110
152
|
doing nothing. Only an already-committed transaction is a no-op.
|
|
153
|
+
- Rolling back a committed transaction throws `TransactionStateError`: the
|
|
154
|
+
commit cannot be undone, and pretending otherwise hid bugs. Rolling back a
|
|
155
|
+
transaction that is already rolled back (or failed) is still a no-op.
|
|
111
156
|
- A `nested` transaction rolls back to its savepoint, never to the connection.
|
|
112
157
|
Savepoints are always created on the connection, including when the
|
|
113
158
|
enclosing scope is itself a savepoint or a participant.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public, read-only access to a transaction's adapter handle.
|
|
3
|
+
*
|
|
4
|
+
* @module context/context.handle
|
|
5
|
+
*/
|
|
6
|
+
import type { Transaction } from "../transactionTypes/transaction.interface.js";
|
|
7
|
+
import type { TransactionContext } from "../transactionTypes/transactionAdapter.js";
|
|
8
|
+
/**
|
|
9
|
+
* The adapter handle a transaction runs on: whatever the adapter's
|
|
10
|
+
* `begin()` returned (for example a database client or connection bound to
|
|
11
|
+
* the transaction).
|
|
12
|
+
*
|
|
13
|
+
* A savepoint resolves to its connection's handle and a participant to the
|
|
14
|
+
* handle of the transaction it joined. A non-transactional scope
|
|
15
|
+
* (`supports`, `not_supported`, `never`) has none and yields `undefined`.
|
|
16
|
+
*
|
|
17
|
+
* The handle is for issuing work inside the transaction. Committing or
|
|
18
|
+
* rolling back through it directly bypasses the manager's state machine,
|
|
19
|
+
* hooks and events; use the manager for that.
|
|
20
|
+
*
|
|
21
|
+
* @typeParam THandle - The handle type your adapter's `begin()` returns.
|
|
22
|
+
* @throws {TypeError} when the transaction was not created by this package.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getTransactionHandle<THandle = unknown>(transaction: Transaction): THandle | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The adapter handle of the transaction in scope for the current async
|
|
27
|
+
* execution, or `undefined` outside a transaction.
|
|
28
|
+
*
|
|
29
|
+
* Uses the default context unless one is supplied; pass the same context
|
|
30
|
+
* the manager was created with when it was given a custom one (or call the
|
|
31
|
+
* manager's `getCurrentHandle()`).
|
|
32
|
+
*
|
|
33
|
+
* @typeParam THandle - The handle type your adapter's `begin()` returns.
|
|
34
|
+
*/
|
|
35
|
+
export declare function currentTransactionHandle<THandle = unknown>(context?: TransactionContext): THandle | undefined;
|
|
36
|
+
//# sourceMappingURL=context.handle.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public, read-only access to a transaction's adapter handle.
|
|
3
|
+
*
|
|
4
|
+
* @module context/context.handle
|
|
5
|
+
*/
|
|
6
|
+
import { connectionHandle } from "../transaction/transaction.internal.js";
|
|
7
|
+
import { getDefaultContext } from "./context.core.js";
|
|
8
|
+
/**
|
|
9
|
+
* The adapter handle a transaction runs on: whatever the adapter's
|
|
10
|
+
* `begin()` returned (for example a database client or connection bound to
|
|
11
|
+
* the transaction).
|
|
12
|
+
*
|
|
13
|
+
* A savepoint resolves to its connection's handle and a participant to the
|
|
14
|
+
* handle of the transaction it joined. A non-transactional scope
|
|
15
|
+
* (`supports`, `not_supported`, `never`) has none and yields `undefined`.
|
|
16
|
+
*
|
|
17
|
+
* The handle is for issuing work inside the transaction. Committing or
|
|
18
|
+
* rolling back through it directly bypasses the manager's state machine,
|
|
19
|
+
* hooks and events; use the manager for that.
|
|
20
|
+
*
|
|
21
|
+
* @typeParam THandle - The handle type your adapter's `begin()` returns.
|
|
22
|
+
* @throws {TypeError} when the transaction was not created by this package.
|
|
23
|
+
*/
|
|
24
|
+
export function getTransactionHandle(transaction) {
|
|
25
|
+
return connectionHandle(transaction);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The adapter handle of the transaction in scope for the current async
|
|
29
|
+
* execution, or `undefined` outside a transaction.
|
|
30
|
+
*
|
|
31
|
+
* Uses the default context unless one is supplied; pass the same context
|
|
32
|
+
* the manager was created with when it was given a custom one (or call the
|
|
33
|
+
* manager's `getCurrentHandle()`).
|
|
34
|
+
*
|
|
35
|
+
* @typeParam THandle - The handle type your adapter's `begin()` returns.
|
|
36
|
+
*/
|
|
37
|
+
export function currentTransactionHandle(context = getDefaultContext()) {
|
|
38
|
+
const transaction = context.get();
|
|
39
|
+
return transaction ? getTransactionHandle(transaction) : undefined;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=context.handle.js.map
|
package/dist/context/index.d.ts
CHANGED
package/dist/context/index.js
CHANGED
|
@@ -20,7 +20,9 @@ import type { TransactionEmitter } from "./manager.events.js";
|
|
|
20
20
|
* is a programming error, not a no-op.
|
|
21
21
|
*
|
|
22
22
|
* @throws {TransactionTimeoutError} when the transaction outlived its timeout.
|
|
23
|
-
* @throws {
|
|
23
|
+
* @throws {TransactionRollbackOnlyError} when the transaction is rollback-only
|
|
24
|
+
* (a `TransactionRollbackError` subclass): the commit was refused and the
|
|
25
|
+
* transaction rolled back.
|
|
24
26
|
* @throws {TransactionStateError} when the transaction cannot be committed.
|
|
25
27
|
* @throws {TransactionCommitError} when the adapter refuses the commit.
|
|
26
28
|
*/
|
|
@@ -28,6 +30,12 @@ export declare function commitTransaction(transaction: Transaction, adapter: Tra
|
|
|
28
30
|
/**
|
|
29
31
|
* Rollback a transaction with hooks and adapter coordination.
|
|
30
32
|
*
|
|
33
|
+
* Rolling back a transaction that is already rolled back or failed is a
|
|
34
|
+
* no-op; a committed transaction cannot be undone, so asking to roll it
|
|
35
|
+
* back is a programming error (the same rule `Transaction.rollback()`
|
|
36
|
+
* enforces) rather than a silent no-op.
|
|
37
|
+
*
|
|
38
|
+
* @throws {TransactionStateError} when the transaction is already committed.
|
|
31
39
|
* @throws {TransactionRollbackError} when the adapter refuses the rollback.
|
|
32
40
|
*/
|
|
33
41
|
export declare function rollbackTransaction(transaction: Transaction, adapter: TransactionAdapter, reason?: unknown, hooks?: TransactionHooks, emit?: TransactionEmitter): Promise<void>;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { asSavepointHandle, internals, } from "../transaction/transaction.internal.js";
|
|
11
11
|
import { canTransition } from "../transaction/transactionStateMachine.js";
|
|
12
|
-
import { SavepointError, TransactionCommitError, TransactionRollbackError, TransactionStateError, TransactionTimeoutError, } from "../transactionErrors/transactionError.types.js";
|
|
12
|
+
import { SavepointError, TransactionCommitError, TransactionRollbackError, TransactionRollbackOnlyError, TransactionStateError, TransactionTimeoutError, } from "../transactionErrors/transactionError.types.js";
|
|
13
13
|
import { noopEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
|
|
14
14
|
/** Moves a transaction to `failed` when the state machine allows it. */
|
|
15
15
|
function markFailed(transaction) {
|
|
@@ -65,7 +65,9 @@ async function adapterRollback(transaction, adapter, reason) {
|
|
|
65
65
|
* is a programming error, not a no-op.
|
|
66
66
|
*
|
|
67
67
|
* @throws {TransactionTimeoutError} when the transaction outlived its timeout.
|
|
68
|
-
* @throws {
|
|
68
|
+
* @throws {TransactionRollbackOnlyError} when the transaction is rollback-only
|
|
69
|
+
* (a `TransactionRollbackError` subclass): the commit was refused and the
|
|
70
|
+
* transaction rolled back.
|
|
69
71
|
* @throws {TransactionStateError} when the transaction cannot be committed.
|
|
70
72
|
* @throws {TransactionCommitError} when the adapter refuses the commit.
|
|
71
73
|
*/
|
|
@@ -85,15 +87,12 @@ export async function commitTransaction(transaction, adapter, hooks, emit = noop
|
|
|
85
87
|
const reason = internals(transaction)._getRollbackOnlyReason() ?? "marked rollback-only";
|
|
86
88
|
await rollbackTransaction(transaction, adapter, reason, hooks, emit);
|
|
87
89
|
// A timeout is a distinct failure from a caller marking the transaction
|
|
88
|
-
// rollback-only
|
|
89
|
-
//
|
|
90
|
+
// rollback-only. `timed_out` was already emitted by the timer when the
|
|
91
|
+
// timeout fired; emitting it again here reported one timeout twice.
|
|
90
92
|
if (transaction.timedOut) {
|
|
91
|
-
emit(TRANSACTION_EVENTS.TIMED_OUT, transaction, reason);
|
|
92
93
|
throw new TransactionTimeoutError(transaction.id, transaction.options.timeout ?? 0);
|
|
93
94
|
}
|
|
94
|
-
throw new
|
|
95
|
-
originalError: reason,
|
|
96
|
-
});
|
|
95
|
+
throw new TransactionRollbackOnlyError(transaction.id, reason);
|
|
97
96
|
}
|
|
98
97
|
if (hooks?.beforeCommit)
|
|
99
98
|
await hooks.beforeCommit({ transaction });
|
|
@@ -133,6 +132,12 @@ export async function commitTransaction(transaction, adapter, hooks, emit = noop
|
|
|
133
132
|
/**
|
|
134
133
|
* Rollback a transaction with hooks and adapter coordination.
|
|
135
134
|
*
|
|
135
|
+
* Rolling back a transaction that is already rolled back or failed is a
|
|
136
|
+
* no-op; a committed transaction cannot be undone, so asking to roll it
|
|
137
|
+
* back is a programming error (the same rule `Transaction.rollback()`
|
|
138
|
+
* enforces) rather than a silent no-op.
|
|
139
|
+
*
|
|
140
|
+
* @throws {TransactionStateError} when the transaction is already committed.
|
|
136
141
|
* @throws {TransactionRollbackError} when the adapter refuses the rollback.
|
|
137
142
|
*/
|
|
138
143
|
export async function rollbackTransaction(transaction, adapter, reason, hooks, emit = noopEmitter) {
|
|
@@ -140,9 +145,10 @@ export async function rollbackTransaction(transaction, adapter, reason, hooks, e
|
|
|
140
145
|
await transaction.rollback(reason);
|
|
141
146
|
return;
|
|
142
147
|
}
|
|
143
|
-
if (transaction.state === "committed"
|
|
144
|
-
transaction.state
|
|
145
|
-
|
|
148
|
+
if (transaction.state === "committed") {
|
|
149
|
+
throw new TransactionStateError(transaction.state, "rollback");
|
|
150
|
+
}
|
|
151
|
+
if (transaction.state === "rolled_back" || transaction.state === "failed") {
|
|
146
152
|
return;
|
|
147
153
|
}
|
|
148
154
|
if (hooks?.beforeRollback)
|
|
@@ -39,6 +39,12 @@ export declare function createTransactionManager(options: TransactionManagerOpti
|
|
|
39
39
|
* Only a transaction this call opened is completed here: joining an
|
|
40
40
|
* enclosing transaction must not commit it, and a failure inside a
|
|
41
41
|
* participant marks the enclosing transaction rollback-only instead.
|
|
42
|
+
*
|
|
43
|
+
* When the transaction times out, `transaction.signal` aborts and this
|
|
44
|
+
* stops waiting for the callback: the transaction is rolled back and
|
|
45
|
+
* the call rejects with `TransactionTimeoutError`. An error raised
|
|
46
|
+
* after a successful commit (for example by an `afterCommit` hook) is
|
|
47
|
+
* rethrown without attempting a rollback.
|
|
42
48
|
*/
|
|
43
49
|
run<T>(callback: (transaction: Transaction) => Promise<T>, opts?: TransactionOptions): Promise<T>;
|
|
44
50
|
/**
|
|
@@ -53,5 +59,11 @@ export declare function createTransactionManager(options: TransactionManagerOpti
|
|
|
53
59
|
rollback(transaction: Transaction, reason?: unknown): Promise<void>;
|
|
54
60
|
/** The transaction in scope for the current async execution, if any. */
|
|
55
61
|
getCurrent(): Transaction | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The adapter handle (what `adapter.begin()` returned) of the
|
|
64
|
+
* transaction in scope for the current async execution, or `undefined`
|
|
65
|
+
* outside a transaction. See `getTransactionHandle`.
|
|
66
|
+
*/
|
|
67
|
+
getCurrentHandle<THandle = unknown>(): THandle | undefined;
|
|
56
68
|
};
|
|
57
69
|
//# sourceMappingURL=manager.core.d.ts.map
|
|
@@ -7,6 +7,8 @@ import { getDefaultContext } from "../context/context.core.js";
|
|
|
7
7
|
import { internals } from "../transaction/transaction.internal.js";
|
|
8
8
|
import { isTerminal } from "../transaction/transactionStateMachine.js";
|
|
9
9
|
import { TransactionRollbackError } from "../transactionErrors/transactionError.types.js";
|
|
10
|
+
import { getTransactionHandle } from "../context/context.handle.js";
|
|
11
|
+
import { raceSignal } from "../utils/utils.signal.js";
|
|
10
12
|
import { commitTransaction, rollbackTransaction } from "./manager.commit.js";
|
|
11
13
|
import { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
|
|
12
14
|
import { withRetry } from "./manager.retry.js";
|
|
@@ -90,6 +92,12 @@ export function createTransactionManager(options) {
|
|
|
90
92
|
* Only a transaction this call opened is completed here: joining an
|
|
91
93
|
* enclosing transaction must not commit it, and a failure inside a
|
|
92
94
|
* participant marks the enclosing transaction rollback-only instead.
|
|
95
|
+
*
|
|
96
|
+
* When the transaction times out, `transaction.signal` aborts and this
|
|
97
|
+
* stops waiting for the callback: the transaction is rolled back and
|
|
98
|
+
* the call rejects with `TransactionTimeoutError`. An error raised
|
|
99
|
+
* after a successful commit (for example by an `afterCommit` hook) is
|
|
100
|
+
* rethrown without attempting a rollback.
|
|
93
101
|
*/
|
|
94
102
|
async run(callback, opts) {
|
|
95
103
|
// A failed attempt that only JOINED an enclosing transaction has not
|
|
@@ -109,11 +117,13 @@ export function createTransactionManager(options) {
|
|
|
109
117
|
joined = transaction.kind === "participant";
|
|
110
118
|
const body = async () => {
|
|
111
119
|
try {
|
|
112
|
-
const result = await callback(transaction);
|
|
120
|
+
const result = await raceSignal(callback(transaction), transaction.signal);
|
|
113
121
|
await this.commit(transaction);
|
|
114
122
|
return result;
|
|
115
123
|
}
|
|
116
124
|
catch (error) {
|
|
125
|
+
if (transaction.state === "committed")
|
|
126
|
+
throw error;
|
|
117
127
|
try {
|
|
118
128
|
await this.rollback(transaction, error);
|
|
119
129
|
}
|
|
@@ -166,6 +176,15 @@ export function createTransactionManager(options) {
|
|
|
166
176
|
getCurrent() {
|
|
167
177
|
return context.get();
|
|
168
178
|
},
|
|
179
|
+
/**
|
|
180
|
+
* The adapter handle (what `adapter.begin()` returned) of the
|
|
181
|
+
* transaction in scope for the current async execution, or `undefined`
|
|
182
|
+
* outside a transaction. See `getTransactionHandle`.
|
|
183
|
+
*/
|
|
184
|
+
getCurrentHandle() {
|
|
185
|
+
const current = context.get();
|
|
186
|
+
return current ? getTransactionHandle(current) : undefined;
|
|
187
|
+
},
|
|
169
188
|
};
|
|
170
189
|
}
|
|
171
190
|
//# sourceMappingURL=manager.core.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core Transaction implementation with state machine enforcement.
|
|
3
3
|
*/
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
|
-
import { TransactionRollbackError, TransactionStateError, } from "../transactionErrors/transactionError.types.js";
|
|
5
|
+
import { TransactionRollbackError, TransactionRollbackOnlyError, TransactionStateError, TransactionTimeoutError, } from "../transactionErrors/transactionError.types.js";
|
|
6
6
|
import { canTransition, createTransitionFunction, } from "./transactionStateMachine.js";
|
|
7
7
|
import { attachInternals } from "./transaction.internal.js";
|
|
8
8
|
import { deferCallbacksToParent } from "./transaction.savepoint.js";
|
|
@@ -43,6 +43,10 @@ export function createTransaction(options = {}, parentId, kind = "root", parent)
|
|
|
43
43
|
const afterRollbackCallbacks = [];
|
|
44
44
|
let callbackErrors = [];
|
|
45
45
|
let handle;
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const signal = parent
|
|
48
|
+
? AbortSignal.any([controller.signal, parent.signal])
|
|
49
|
+
: controller.signal;
|
|
46
50
|
const transition = createTransitionFunction(() => state, (next) => {
|
|
47
51
|
state = next;
|
|
48
52
|
});
|
|
@@ -75,6 +79,9 @@ export function createTransaction(options = {}, parentId, kind = "root", parent)
|
|
|
75
79
|
get timedOut() {
|
|
76
80
|
return timedOut;
|
|
77
81
|
},
|
|
82
|
+
get signal() {
|
|
83
|
+
return signal;
|
|
84
|
+
},
|
|
78
85
|
/**
|
|
79
86
|
* Mark the transaction committed and run its after-commit callbacks.
|
|
80
87
|
*
|
|
@@ -88,9 +95,7 @@ export function createTransaction(options = {}, parentId, kind = "root", parent)
|
|
|
88
95
|
throw new TransactionStateError(state, "commit");
|
|
89
96
|
}
|
|
90
97
|
if (rollbackOnly) {
|
|
91
|
-
throw new
|
|
92
|
-
originalError: rollbackOnlyReason ?? "marked rollback-only",
|
|
93
|
-
});
|
|
98
|
+
throw new TransactionRollbackOnlyError(id, rollbackOnlyReason);
|
|
94
99
|
}
|
|
95
100
|
transition("committing");
|
|
96
101
|
transition("committed");
|
|
@@ -145,6 +150,7 @@ export function createTransaction(options = {}, parentId, kind = "root", parent)
|
|
|
145
150
|
_transition: transition,
|
|
146
151
|
_markTimedOut: () => {
|
|
147
152
|
timedOut = true;
|
|
153
|
+
controller.abort(new TransactionTimeoutError(id, frozenOptions.timeout ?? 0));
|
|
148
154
|
},
|
|
149
155
|
_getRollbackOnlyReason: () => rollbackOnlyReason,
|
|
150
156
|
_drainCallbackErrors: () => callbackErrors.splice(0),
|
|
@@ -43,6 +43,9 @@ export function createParticipant(parent) {
|
|
|
43
43
|
get timedOut() {
|
|
44
44
|
return parent.timedOut;
|
|
45
45
|
},
|
|
46
|
+
get signal() {
|
|
47
|
+
return parent.signal;
|
|
48
|
+
},
|
|
46
49
|
/** No-op: the transaction is committed by whoever opened it. */
|
|
47
50
|
async commit() { },
|
|
48
51
|
/** Marks the joined transaction rollback-only. */
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Transaction error types.
|
|
3
3
|
*/
|
|
4
4
|
export { TransactionError, type TransactionErrorOptions, } from "./transactionError.base.js";
|
|
5
|
-
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
5
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionRollbackOnlyError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Transaction error types.
|
|
3
3
|
*/
|
|
4
4
|
export { TransactionError, } from "./transactionError.base.js";
|
|
5
|
-
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
5
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionRollbackOnlyError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "./transactionError.types.js";
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* same names, constructor signatures and codes, so `instanceof` checks match
|
|
6
6
|
* across both import paths.
|
|
7
7
|
*/
|
|
8
|
-
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
8
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionRollbackOnlyError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
9
9
|
//# sourceMappingURL=transactionError.types.d.ts.map
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* same names, constructor signatures and codes, so `instanceof` checks match
|
|
6
6
|
* across both import paths.
|
|
7
7
|
*/
|
|
8
|
-
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
8
|
+
export { TransactionStateError, TransactionTimeoutError, TransactionCommitError, TransactionRollbackError, TransactionRollbackOnlyError, TransactionAdapterError, TransactionPropagationError, TransactionIsolationError, SavepointError, TransactionRequiredError, TransactionUnexpectedError, TransactionCapabilityError, } from "@zudojs/errors";
|
|
9
9
|
//# sourceMappingURL=transactionError.types.js.map
|
|
@@ -64,9 +64,23 @@ export interface Transaction {
|
|
|
64
64
|
readonly metadata: ReadonlyMap<string, unknown>;
|
|
65
65
|
/** Whether a timeout was detected. */
|
|
66
66
|
readonly timedOut: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Aborted when the transaction outlives its `timeout`, with a
|
|
69
|
+
* `TransactionTimeoutError` as `signal.reason`. Pass it to cancellable
|
|
70
|
+
* work (queries, `fetch`, timers) so a timed-out callback stops instead
|
|
71
|
+
* of running on. `run()` also stops waiting for the callback, rolls back
|
|
72
|
+
* and rejects with that error. A participant exposes the signal of the
|
|
73
|
+
* transaction it joined; a savepoint's signal also aborts with its
|
|
74
|
+
* parent's. Never aborts when no timeout is set.
|
|
75
|
+
*/
|
|
76
|
+
readonly signal: AbortSignal;
|
|
67
77
|
/** Commit the transaction. */
|
|
68
78
|
commit(): Promise<void>;
|
|
69
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Rollback the transaction. Idempotent once rolled back or failed; throws
|
|
81
|
+
* `TransactionStateError` on a committed transaction, which can no
|
|
82
|
+
* longer be undone.
|
|
83
|
+
*/
|
|
70
84
|
rollback(reason?: unknown): Promise<void>;
|
|
71
85
|
/** Mark the transaction as rollback-only (prevents commit). */
|
|
72
86
|
markRollbackOnly(reason?: unknown): void;
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort-signal helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module utils/utils.signal
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Resolves or rejects with `work`, or rejects with `signal.reason` as soon
|
|
8
|
+
* as `signal` aborts, whichever happens first.
|
|
9
|
+
*
|
|
10
|
+
* The abandoned `work` promise keeps running (JavaScript cannot cancel it);
|
|
11
|
+
* its eventual rejection is observed so it never surfaces as an unhandled
|
|
12
|
+
* rejection. Cooperative code should watch the same signal and stop.
|
|
13
|
+
*/
|
|
14
|
+
export declare function raceSignal<T>(work: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
15
|
+
//# sourceMappingURL=utils.signal.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort-signal helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module utils/utils.signal
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Resolves or rejects with `work`, or rejects with `signal.reason` as soon
|
|
8
|
+
* as `signal` aborts, whichever happens first.
|
|
9
|
+
*
|
|
10
|
+
* The abandoned `work` promise keeps running (JavaScript cannot cancel it);
|
|
11
|
+
* its eventual rejection is observed so it never surfaces as an unhandled
|
|
12
|
+
* rejection. Cooperative code should watch the same signal and stop.
|
|
13
|
+
*/
|
|
14
|
+
export function raceSignal(work, signal) {
|
|
15
|
+
if (signal.aborted) {
|
|
16
|
+
work.catch(() => undefined);
|
|
17
|
+
return Promise.reject(signal.reason);
|
|
18
|
+
}
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const onAbort = () => {
|
|
21
|
+
work.catch(() => undefined);
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
25
|
+
work.then((value) => {
|
|
26
|
+
signal.removeEventListener("abort", onAbort);
|
|
27
|
+
resolve(value);
|
|
28
|
+
}, (error) => {
|
|
29
|
+
signal.removeEventListener("abort", onAbort);
|
|
30
|
+
reject(error);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=utils.signal.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/transactions",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Transaction lifecycle and coordination with state machine, AsyncLocalStorage context propagation, savepoints, hooks, and adapter abstraction.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"!dist/.tsbuildinfo"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@zudojs/errors": "1.
|
|
28
|
+
"@zudojs/errors": "1.3.1"
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=24.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
|
-
"vitest": "^
|
|
35
|
+
"vitest": "^5.0.1"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"savepoints",
|
|
44
44
|
"rollback"
|
|
45
45
|
],
|
|
46
|
-
"homepage": "https://
|
|
46
|
+
"homepage": "https://zudojs.oyinlola.site/docs/packages-transactions",
|
|
47
47
|
"bugs": {
|
|
48
48
|
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
49
49
|
},
|