@zudojs/transactions 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -11
- package/dist/adapter/adapter.core.d.ts +4 -0
- package/dist/adapter/adapter.core.js +45 -9
- package/dist/context/context.core.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/manager/index.d.ts +7 -0
- package/dist/manager/index.js +5 -0
- package/dist/manager/manager.capabilities.d.ts +26 -0
- package/dist/manager/manager.capabilities.js +41 -0
- package/dist/manager/manager.commit.d.ts +19 -2
- package/dist/manager/manager.commit.js +112 -24
- package/dist/manager/manager.core.d.ts +28 -3
- package/dist/manager/manager.core.js +75 -53
- package/dist/manager/manager.events.d.ts +31 -0
- package/dist/manager/manager.events.js +45 -0
- package/dist/manager/manager.propagation.d.ts +33 -5
- package/dist/manager/manager.propagation.js +100 -36
- package/dist/manager/manager.retry.d.ts +20 -0
- package/dist/manager/manager.retry.js +46 -0
- package/dist/transaction/index.d.ts +5 -3
- package/dist/transaction/index.js +4 -3
- package/dist/transaction/transaction.core.d.ts +6 -1
- package/dist/transaction/transaction.core.js +60 -57
- package/dist/transaction/transaction.internal.d.ts +65 -0
- package/dist/transaction/transaction.internal.js +66 -0
- package/dist/transaction/transaction.participant.d.ts +31 -0
- package/dist/transaction/transaction.participant.js +81 -0
- package/dist/transaction/transactionStateMachine.d.ts +10 -0
- package/dist/transaction/transactionStateMachine.js +16 -2
- package/dist/transactionTypes/index.d.ts +4 -4
- package/dist/transactionTypes/transaction.interface.d.ts +19 -1
- package/dist/transactionTypes/transactionAdapter.d.ts +7 -9
- package/dist/transactionTypes/transactionHooks.d.ts +0 -7
- package/dist/transactionTypes/transactionState.d.ts +9 -0
- package/dist/utils/utils.helper.d.ts +6 -1
- package/dist/utils/utils.helper.js +8 -2
- package/package.json +20 -13
- package/dist/.tsbuildinfo +0 -1
- package/dist/adapter/adapter.core.d.ts.map +0 -1
- package/dist/adapter/adapter.core.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js.map +0 -1
- package/dist/context/context.core.d.ts.map +0 -1
- package/dist/context/context.core.js.map +0 -1
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js.map +0 -1
- package/dist/hooks/hooks.core.d.ts.map +0 -1
- package/dist/hooks/hooks.core.js.map +0 -1
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/manager/index.d.ts.map +0 -1
- package/dist/manager/index.js.map +0 -1
- package/dist/manager/manager.commit.d.ts.map +0 -1
- package/dist/manager/manager.commit.js.map +0 -1
- package/dist/manager/manager.core.d.ts.map +0 -1
- package/dist/manager/manager.core.js.map +0 -1
- package/dist/manager/manager.propagation.d.ts.map +0 -1
- package/dist/manager/manager.propagation.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/registry.core.d.ts.map +0 -1
- package/dist/registry/registry.core.js.map +0 -1
- package/dist/transaction/index.d.ts.map +0 -1
- package/dist/transaction/index.js.map +0 -1
- package/dist/transaction/transaction.core.d.ts.map +0 -1
- package/dist/transaction/transaction.core.js.map +0 -1
- package/dist/transaction/transactionStateMachine.d.ts.map +0 -1
- package/dist/transaction/transactionStateMachine.js.map +0 -1
- package/dist/transactionErrors/index.d.ts.map +0 -1
- package/dist/transactionErrors/index.js.map +0 -1
- package/dist/transactionErrors/transactionError.base.d.ts.map +0 -1
- package/dist/transactionErrors/transactionError.base.js.map +0 -1
- package/dist/transactionErrors/transactionError.types.d.ts.map +0 -1
- package/dist/transactionErrors/transactionError.types.js.map +0 -1
- package/dist/transactionTypes/index.d.ts.map +0 -1
- package/dist/transactionTypes/index.js.map +0 -1
- package/dist/transactionTypes/transaction.interface.d.ts.map +0 -1
- package/dist/transactionTypes/transaction.interface.js.map +0 -1
- package/dist/transactionTypes/transactionAdapter.d.ts.map +0 -1
- package/dist/transactionTypes/transactionAdapter.js.map +0 -1
- package/dist/transactionTypes/transactionHooks.d.ts.map +0 -1
- package/dist/transactionTypes/transactionHooks.js.map +0 -1
- package/dist/transactionTypes/transactionState.d.ts.map +0 -1
- package/dist/transactionTypes/transactionState.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/utils.helper.d.ts.map +0 -1
- package/dist/utils/utils.helper.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zudojs Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -13,28 +13,99 @@ npm install @zudojs/transactions
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { createTransactionManager } from "@zudojs/transactions";
|
|
15
15
|
|
|
16
|
-
const manager = createTransactionManager({
|
|
17
|
-
|
|
16
|
+
const manager = createTransactionManager({ adapter: databaseAdapter });
|
|
17
|
+
|
|
18
|
+
// `run` opens a transaction, commits on success, rolls back on a throw.
|
|
19
|
+
const orderId = await manager.run(async (transaction) => {
|
|
20
|
+
await insertOrder(transaction);
|
|
21
|
+
await insertLineItems(transaction);
|
|
22
|
+
return "o_1";
|
|
18
23
|
});
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
await
|
|
25
|
+
// A nested `run` joins the enclosing transaction rather than completing it.
|
|
26
|
+
await manager.run(async () => {
|
|
27
|
+
await manager.run(async (participant) => {
|
|
28
|
+
// participant.kind === "participant"; committing it is a no-op, and a
|
|
29
|
+
// throw here marks the enclosing transaction rollback-only.
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Retries replay the whole unit of work.
|
|
34
|
+
await manager.run(handler, {
|
|
35
|
+
retry: {
|
|
36
|
+
attempts: 3,
|
|
37
|
+
backoff: "exponential",
|
|
38
|
+
delay: 50,
|
|
39
|
+
shouldRetry: (error) => isSerializationFailure(error),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Propagation
|
|
45
|
+
|
|
46
|
+
| Mode | Transaction in progress | None in progress |
|
|
47
|
+
| --------------- | ---------------------------- | ------------------------ |
|
|
48
|
+
| `required` | joins it as a participant | opens a new one |
|
|
49
|
+
| `requires_new` | suspends it, opens a new one | opens a new one |
|
|
50
|
+
| `nested` | opens a savepoint on it | opens a new one |
|
|
51
|
+
| `supports` | joins it as a participant | runs non-transactionally |
|
|
52
|
+
| `not_supported` | suspends it | runs non-transactionally |
|
|
53
|
+
| `mandatory` | joins it as a participant | throws |
|
|
54
|
+
| `never` | throws | runs non-transactionally |
|
|
55
|
+
|
|
56
|
+
## Lifecycle events
|
|
57
|
+
|
|
58
|
+
Pass `onEvent` to observe the lifecycle. Each event names a member of
|
|
59
|
+
`TRANSACTION_EVENTS` and carries the transaction id, a timestamp and the
|
|
60
|
+
elapsed duration:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const manager = createTransactionManager({
|
|
64
|
+
adapter,
|
|
65
|
+
onEvent: (event) => metrics.increment(event.type, { id: event.transactionId }),
|
|
23
66
|
});
|
|
24
67
|
```
|
|
25
68
|
|
|
69
|
+
Emitted: `started`, `committing`, `committed`, `rolling_back`, `rolled_back`,
|
|
70
|
+
`failed` and `timed_out`. A throwing observer is ignored rather than failing
|
|
71
|
+
the transaction.
|
|
72
|
+
|
|
73
|
+
## Errors
|
|
74
|
+
|
|
75
|
+
| Condition | Error |
|
|
76
|
+
| -------------------------------------- | ------------------------------ |
|
|
77
|
+
| Transaction outlived its `timeout` | `TransactionTimeoutError` |
|
|
78
|
+
| Marked rollback-only, then committed | `TransactionRollbackError` |
|
|
79
|
+
| Adapter lacks the requested isolation | `TransactionIsolationError` |
|
|
80
|
+
| Adapter lacks another requested feature | `TransactionCapabilityError` |
|
|
81
|
+
| Savepoint create/rollback/release fails | `SavepointError` |
|
|
82
|
+
| Propagation precondition violated | `TransactionPropagationError` |
|
|
83
|
+
| Operation invalid for the current state | `TransactionStateError` |
|
|
84
|
+
| Adapter refused the commit | `TransactionCommitError` |
|
|
85
|
+
| Adapter itself failed | `TransactionAdapterError` |
|
|
86
|
+
|
|
26
87
|
## Features
|
|
27
88
|
|
|
28
|
-
- Transaction state machine
|
|
29
|
-
- AsyncLocalStorage context propagation
|
|
30
|
-
- Savepoints for nested transactions
|
|
89
|
+
- Transaction state machine with enforced transitions
|
|
90
|
+
- AsyncLocalStorage context propagation, with suspension
|
|
91
|
+
- Savepoints for nested transactions, released on commit
|
|
31
92
|
- Before/after hooks
|
|
32
|
-
- Adapter abstraction
|
|
33
|
-
-
|
|
93
|
+
- Adapter abstraction with capability enforcement
|
|
94
|
+
- Retry with fixed or exponential backoff
|
|
95
|
+
- Rollback-only and timeout semantics
|
|
96
|
+
- Lifecycle events for observability
|
|
97
|
+
|
|
98
|
+
## Safety Notes
|
|
99
|
+
|
|
100
|
+
- The rollback-only flag is read **before** the adapter is asked to commit, so
|
|
101
|
+
a transaction marked rollback-only — including one that timed out — is rolled
|
|
102
|
+
back and `commit()` rejects. A rollback can never be reported as a commit.
|
|
103
|
+
- Committing a transaction that is not active throws rather than silently
|
|
104
|
+
doing nothing. Only an already-committed transaction is a no-op.
|
|
105
|
+
- A `nested` transaction rolls back to its savepoint, never to the connection.
|
|
34
106
|
|
|
35
107
|
## Use Cases
|
|
36
108
|
|
|
37
109
|
- Database transaction management
|
|
38
|
-
- Distributed transaction coordination
|
|
39
110
|
- Unit of Work pattern
|
|
40
111
|
- Audit logging with transaction context
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import type { TransactionAdapter, TransactionAdapterCapabilities } from "../transactionTypes/transactionAdapter.js";
|
|
7
7
|
/**
|
|
8
8
|
* Create an in-memory transaction adapter (useful for testing).
|
|
9
|
+
*
|
|
10
|
+
* Capabilities are declared honestly: the adapter emulates savepoints and
|
|
11
|
+
* accepts every isolation level, because it enforces none of them and so
|
|
12
|
+
* cannot fail to provide what it claims.
|
|
9
13
|
*/
|
|
10
14
|
export declare function createInMemoryAdapter(): TransactionAdapter;
|
|
11
15
|
/**
|
|
@@ -3,36 +3,72 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module adapter/adapter
|
|
5
5
|
*/
|
|
6
|
+
import { TransactionAdapterError } from "../transactionErrors/transactionError.types.js";
|
|
6
7
|
let counter = 0;
|
|
8
|
+
/** Narrows an opaque handle to an in-memory handle, or fails loudly. */
|
|
9
|
+
function asHandle(handle) {
|
|
10
|
+
if (typeof handle !== "object" ||
|
|
11
|
+
handle === null ||
|
|
12
|
+
!("savepoints" in handle)) {
|
|
13
|
+
throw new TransactionAdapterError("Handle was not issued by the in-memory adapter");
|
|
14
|
+
}
|
|
15
|
+
return handle;
|
|
16
|
+
}
|
|
7
17
|
/**
|
|
8
18
|
* Create an in-memory transaction adapter (useful for testing).
|
|
19
|
+
*
|
|
20
|
+
* Capabilities are declared honestly: the adapter emulates savepoints and
|
|
21
|
+
* accepts every isolation level, because it enforces none of them and so
|
|
22
|
+
* cannot fail to provide what it claims.
|
|
9
23
|
*/
|
|
10
24
|
export function createInMemoryAdapter() {
|
|
11
25
|
const handles = new Map();
|
|
12
26
|
return {
|
|
13
27
|
capabilities: Object.freeze({
|
|
14
|
-
savepoints:
|
|
15
|
-
nestedTransactions:
|
|
16
|
-
isolationLevels: [
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
savepoints: true,
|
|
29
|
+
nestedTransactions: true,
|
|
30
|
+
isolationLevels: [
|
|
31
|
+
"read_uncommitted",
|
|
32
|
+
"read_committed",
|
|
33
|
+
"repeatable_read",
|
|
34
|
+
"serializable",
|
|
35
|
+
],
|
|
36
|
+
readOnlyTransactions: true,
|
|
37
|
+
timeouts: true,
|
|
19
38
|
}),
|
|
20
|
-
async begin() {
|
|
39
|
+
async begin(_options) {
|
|
21
40
|
const id = `mem_${++counter}`;
|
|
22
|
-
const handle = { id, active: true };
|
|
41
|
+
const handle = { id, active: true, savepoints: [] };
|
|
23
42
|
handles.set(id, handle);
|
|
24
43
|
return handle;
|
|
25
44
|
},
|
|
26
45
|
async commit(handle) {
|
|
27
|
-
const h = handle;
|
|
46
|
+
const h = asHandle(handle);
|
|
28
47
|
h.active = false;
|
|
29
48
|
handles.delete(h.id);
|
|
30
49
|
},
|
|
31
50
|
async rollback(handle) {
|
|
32
|
-
const h = handle;
|
|
51
|
+
const h = asHandle(handle);
|
|
33
52
|
h.active = false;
|
|
34
53
|
handles.delete(h.id);
|
|
35
54
|
},
|
|
55
|
+
async createSavepoint(handle, name) {
|
|
56
|
+
asHandle(handle).savepoints.push(name);
|
|
57
|
+
},
|
|
58
|
+
async rollbackToSavepoint(handle, name) {
|
|
59
|
+
const h = asHandle(handle);
|
|
60
|
+
const index = h.savepoints.indexOf(name);
|
|
61
|
+
if (index === -1) {
|
|
62
|
+
throw new TransactionAdapterError(`Unknown savepoint: ${name}`);
|
|
63
|
+
}
|
|
64
|
+
h.savepoints.length = index + 1;
|
|
65
|
+
},
|
|
66
|
+
async releaseSavepoint(handle, name) {
|
|
67
|
+
const h = asHandle(handle);
|
|
68
|
+
const index = h.savepoints.indexOf(name);
|
|
69
|
+
if (index !== -1)
|
|
70
|
+
h.savepoints.splice(index, 1);
|
|
71
|
+
},
|
|
36
72
|
};
|
|
37
73
|
}
|
|
38
74
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -16,5 +16,5 @@ export * from "./adapter/index.js";
|
|
|
16
16
|
export * from "./hooks/index.js";
|
|
17
17
|
export * from "./registry/index.js";
|
|
18
18
|
export * from "./utils/index.js";
|
|
19
|
-
export { createTransactionManager, type TransactionManagerOptions, } from "./manager/index.js";
|
|
19
|
+
export { createTransactionManager, createEmitter, type TransactionEmitter, type TransactionManagerOptions, } from "./manager/index.js";
|
|
20
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -16,5 +16,5 @@ export * from "./adapter/index.js";
|
|
|
16
16
|
export * from "./hooks/index.js";
|
|
17
17
|
export * from "./registry/index.js";
|
|
18
18
|
export * from "./utils/index.js";
|
|
19
|
-
export { createTransactionManager, } from "./manager/index.js";
|
|
19
|
+
export { createTransactionManager, createEmitter, } from "./manager/index.js";
|
|
20
20
|
//# sourceMappingURL=index.js.map
|
package/dist/manager/index.d.ts
CHANGED
|
@@ -4,4 +4,11 @@
|
|
|
4
4
|
* @module manager
|
|
5
5
|
*/
|
|
6
6
|
export { createTransactionManager, type TransactionManagerOptions, } from "./manager.core.js";
|
|
7
|
+
export { commitTransaction, rollbackTransaction } from "./manager.commit.js";
|
|
8
|
+
export { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
|
|
9
|
+
export type { PropagationContext } from "./manager.propagation.js";
|
|
10
|
+
export { assertAdapterSupports } from "./manager.capabilities.js";
|
|
11
|
+
export { withRetry } from "./manager.retry.js";
|
|
12
|
+
export { createEmitter } from "./manager.events.js";
|
|
13
|
+
export type { TransactionEmitter } from "./manager.events.js";
|
|
7
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/manager/index.js
CHANGED
|
@@ -4,4 +4,9 @@
|
|
|
4
4
|
* @module manager
|
|
5
5
|
*/
|
|
6
6
|
export { createTransactionManager, } from "./manager.core.js";
|
|
7
|
+
export { commitTransaction, rollbackTransaction } from "./manager.commit.js";
|
|
8
|
+
export { resolvePropagation, suspendsTransaction, } from "./manager.propagation.js";
|
|
9
|
+
export { assertAdapterSupports } from "./manager.capabilities.js";
|
|
10
|
+
export { withRetry } from "./manager.retry.js";
|
|
11
|
+
export { createEmitter } from "./manager.events.js";
|
|
7
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter capability enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Every adapter declares what it supports. Requesting something it does not
|
|
5
|
+
* provide has to fail loudly: silently running a `serializable` unit of work
|
|
6
|
+
* at the driver's default isolation is worse than not starting it.
|
|
7
|
+
*
|
|
8
|
+
* @module manager/manager.capabilities
|
|
9
|
+
*/
|
|
10
|
+
import type { TransactionOptions } from "../transactionTypes/transaction.interface.js";
|
|
11
|
+
import type { TransactionAdapter } from "../transactionTypes/transactionAdapter.js";
|
|
12
|
+
/**
|
|
13
|
+
* Assert that an adapter can honour the options a transaction requests.
|
|
14
|
+
*
|
|
15
|
+
* @param adapter - The adapter about to begin the transaction.
|
|
16
|
+
* @param options - The requested options.
|
|
17
|
+
* A capability mismatch is a configuration error, not an adapter fault, so it
|
|
18
|
+
* no longer shares `TransactionAdapterError` with genuine driver failures:
|
|
19
|
+
* `TransactionIsolationError` names the level, `TransactionCapabilityError`
|
|
20
|
+
* names the capability.
|
|
21
|
+
*
|
|
22
|
+
* @throws {TransactionIsolationError} when the isolation level is unsupported.
|
|
23
|
+
* @throws {TransactionCapabilityError} when another capability is missing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertAdapterSupports(adapter: TransactionAdapter, options: TransactionOptions | undefined): void;
|
|
26
|
+
//# sourceMappingURL=manager.capabilities.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter capability enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Every adapter declares what it supports. Requesting something it does not
|
|
5
|
+
* provide has to fail loudly: silently running a `serializable` unit of work
|
|
6
|
+
* at the driver's default isolation is worse than not starting it.
|
|
7
|
+
*
|
|
8
|
+
* @module manager/manager.capabilities
|
|
9
|
+
*/
|
|
10
|
+
import { TransactionCapabilityError, TransactionIsolationError, } from "../transactionErrors/transactionError.types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Assert that an adapter can honour the options a transaction requests.
|
|
13
|
+
*
|
|
14
|
+
* @param adapter - The adapter about to begin the transaction.
|
|
15
|
+
* @param options - The requested options.
|
|
16
|
+
* A capability mismatch is a configuration error, not an adapter fault, so it
|
|
17
|
+
* no longer shares `TransactionAdapterError` with genuine driver failures:
|
|
18
|
+
* `TransactionIsolationError` names the level, `TransactionCapabilityError`
|
|
19
|
+
* names the capability.
|
|
20
|
+
*
|
|
21
|
+
* @throws {TransactionIsolationError} when the isolation level is unsupported.
|
|
22
|
+
* @throws {TransactionCapabilityError} when another capability is missing.
|
|
23
|
+
*/
|
|
24
|
+
export function assertAdapterSupports(adapter, options) {
|
|
25
|
+
if (!options)
|
|
26
|
+
return;
|
|
27
|
+
const { capabilities } = adapter;
|
|
28
|
+
if (options.isolation !== undefined &&
|
|
29
|
+
!capabilities.isolationLevels.includes(options.isolation)) {
|
|
30
|
+
throw new TransactionIsolationError(options.isolation);
|
|
31
|
+
}
|
|
32
|
+
if (options.readOnly === true && !capabilities.readOnlyTransactions) {
|
|
33
|
+
throw new TransactionCapabilityError("read-only transactions");
|
|
34
|
+
}
|
|
35
|
+
if (options.timeout !== undefined &&
|
|
36
|
+
options.timeout > 0 &&
|
|
37
|
+
!capabilities.timeouts) {
|
|
38
|
+
throw new TransactionCapabilityError("transaction timeouts");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=manager.capabilities.js.map
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transaction commit and rollback orchestration.
|
|
3
3
|
*
|
|
4
|
+
* Ordering is the whole point of this module. The rollback-only flag is read
|
|
5
|
+
* before the adapter is asked to commit, never after: once `adapter.commit()`
|
|
6
|
+
* has returned there is nothing left to decide.
|
|
7
|
+
*
|
|
4
8
|
* @module manager/manager.commit
|
|
5
9
|
*/
|
|
6
10
|
import type { Transaction } from "../transactionTypes/transaction.interface.js";
|
|
7
11
|
import type { TransactionAdapter } from "../transactionTypes/transactionAdapter.js";
|
|
8
12
|
import type { TransactionHooks } from "../transactionTypes/transactionHooks.js";
|
|
13
|
+
import type { TransactionEmitter } from "./manager.events.js";
|
|
9
14
|
/**
|
|
10
15
|
* Commit a transaction with hooks and adapter coordination.
|
|
16
|
+
*
|
|
17
|
+
* A participant commits nothing — the scope that opened the transaction does
|
|
18
|
+
* that — and a non-transactional scope has no adapter to talk to. Anything
|
|
19
|
+
* else must be active: committing a transaction that was already rolled back
|
|
20
|
+
* is a programming error, not a no-op.
|
|
21
|
+
*
|
|
22
|
+
* @throws {TransactionTimeoutError} when the transaction outlived its timeout.
|
|
23
|
+
* @throws {TransactionRollbackError} when the transaction is rollback-only.
|
|
24
|
+
* @throws {TransactionStateError} when the transaction cannot be committed.
|
|
25
|
+
* @throws {TransactionCommitError} when the adapter refuses the commit.
|
|
11
26
|
*/
|
|
12
|
-
export declare function commitTransaction(transaction: Transaction, adapter: TransactionAdapter, hooks?: TransactionHooks): Promise<void>;
|
|
27
|
+
export declare function commitTransaction(transaction: Transaction, adapter: TransactionAdapter, hooks?: TransactionHooks, emit?: TransactionEmitter): Promise<void>;
|
|
13
28
|
/**
|
|
14
29
|
* Rollback a transaction with hooks and adapter coordination.
|
|
30
|
+
*
|
|
31
|
+
* @throws {TransactionRollbackError} when the adapter refuses the rollback.
|
|
15
32
|
*/
|
|
16
|
-
export declare function rollbackTransaction(transaction: Transaction, adapter: TransactionAdapter, reason?: unknown, hooks?: TransactionHooks): Promise<void>;
|
|
33
|
+
export declare function rollbackTransaction(transaction: Transaction, adapter: TransactionAdapter, reason?: unknown, hooks?: TransactionHooks, emit?: TransactionEmitter): Promise<void>;
|
|
17
34
|
//# sourceMappingURL=manager.commit.d.ts.map
|
|
@@ -1,66 +1,154 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transaction commit and rollback orchestration.
|
|
3
3
|
*
|
|
4
|
+
* Ordering is the whole point of this module. The rollback-only flag is read
|
|
5
|
+
* before the adapter is asked to commit, never after: once `adapter.commit()`
|
|
6
|
+
* has returned there is nothing left to decide.
|
|
7
|
+
*
|
|
4
8
|
* @module manager/manager.commit
|
|
5
9
|
*/
|
|
6
|
-
import {
|
|
10
|
+
import { asSavepointHandle, internals, } from "../transaction/transaction.internal.js";
|
|
11
|
+
import { canTransition } from "../transaction/transactionStateMachine.js";
|
|
12
|
+
import { SavepointError, TransactionCommitError, TransactionRollbackError, TransactionStateError, TransactionTimeoutError, } from "../transactionErrors/transactionError.types.js";
|
|
13
|
+
import { noopEmitter, TRANSACTION_EVENTS } from "./manager.events.js";
|
|
14
|
+
/** Moves a transaction to `failed` when the state machine allows it. */
|
|
15
|
+
function markFailed(transaction) {
|
|
16
|
+
if (canTransition(transaction.state, "failed")) {
|
|
17
|
+
internals(transaction)._transition("failed");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Releases a savepoint, or commits the connection for a root transaction. */
|
|
21
|
+
async function adapterCommit(transaction, adapter) {
|
|
22
|
+
const handle = internals(transaction)._getHandle();
|
|
23
|
+
const savepoint = asSavepointHandle(handle);
|
|
24
|
+
if (savepoint) {
|
|
25
|
+
if (adapter.releaseSavepoint) {
|
|
26
|
+
try {
|
|
27
|
+
await adapter.releaseSavepoint(savepoint.parent, savepoint.savepoint);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new SavepointError(`Failed to release savepoint "${savepoint.savepoint}"`, error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await adapter.commit(handle);
|
|
36
|
+
}
|
|
37
|
+
/** Rolls back to a savepoint, or rolls back the connection. */
|
|
38
|
+
async function adapterRollback(transaction, adapter, reason) {
|
|
39
|
+
const handle = internals(transaction)._getHandle();
|
|
40
|
+
if (handle === undefined)
|
|
41
|
+
return;
|
|
42
|
+
const savepoint = asSavepointHandle(handle);
|
|
43
|
+
if (savepoint) {
|
|
44
|
+
try {
|
|
45
|
+
if (adapter.rollbackToSavepoint) {
|
|
46
|
+
await adapter.rollbackToSavepoint(savepoint.parent, savepoint.savepoint);
|
|
47
|
+
}
|
|
48
|
+
if (adapter.releaseSavepoint) {
|
|
49
|
+
await adapter.releaseSavepoint(savepoint.parent, savepoint.savepoint);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new SavepointError(`Failed to roll back to savepoint "${savepoint.savepoint}"`, error);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await adapter.rollback(handle, reason);
|
|
58
|
+
}
|
|
7
59
|
/**
|
|
8
60
|
* Commit a transaction with hooks and adapter coordination.
|
|
61
|
+
*
|
|
62
|
+
* A participant commits nothing — the scope that opened the transaction does
|
|
63
|
+
* that — and a non-transactional scope has no adapter to talk to. Anything
|
|
64
|
+
* else must be active: committing a transaction that was already rolled back
|
|
65
|
+
* is a programming error, not a no-op.
|
|
66
|
+
*
|
|
67
|
+
* @throws {TransactionTimeoutError} when the transaction outlived its timeout.
|
|
68
|
+
* @throws {TransactionRollbackError} when the transaction is rollback-only.
|
|
69
|
+
* @throws {TransactionStateError} when the transaction cannot be committed.
|
|
70
|
+
* @throws {TransactionCommitError} when the adapter refuses the commit.
|
|
9
71
|
*/
|
|
10
|
-
export async function commitTransaction(transaction, adapter, hooks) {
|
|
11
|
-
if (transaction.
|
|
72
|
+
export async function commitTransaction(transaction, adapter, hooks, emit = noopEmitter) {
|
|
73
|
+
if (transaction.kind === "participant")
|
|
12
74
|
return;
|
|
13
|
-
if (
|
|
14
|
-
|
|
75
|
+
if (transaction.state === "committed")
|
|
76
|
+
return;
|
|
77
|
+
if (transaction.kind === "none") {
|
|
78
|
+
await transaction.commit();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (transaction.state !== "active") {
|
|
82
|
+
throw new TransactionStateError(transaction.state, "commit");
|
|
15
83
|
}
|
|
16
|
-
|
|
84
|
+
if (transaction.isRollbackOnly()) {
|
|
85
|
+
const reason = internals(transaction)._getRollbackOnlyReason() ?? "marked rollback-only";
|
|
86
|
+
await rollbackTransaction(transaction, adapter, reason, hooks, emit);
|
|
87
|
+
// A timeout is a distinct failure from a caller marking the transaction
|
|
88
|
+
// rollback-only, and the timed-out case used to be indistinguishable
|
|
89
|
+
// because both raised TransactionRollbackError.
|
|
90
|
+
if (transaction.timedOut) {
|
|
91
|
+
emit(TRANSACTION_EVENTS.TIMED_OUT, transaction, reason);
|
|
92
|
+
throw new TransactionTimeoutError(transaction.id, transaction.options.timeout ?? 0);
|
|
93
|
+
}
|
|
94
|
+
throw new TransactionRollbackError(transaction.id, {
|
|
95
|
+
originalError: reason,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (hooks?.beforeCommit)
|
|
99
|
+
await hooks.beforeCommit({ transaction });
|
|
100
|
+
emit(TRANSACTION_EVENTS.COMMITTING, transaction);
|
|
17
101
|
try {
|
|
18
|
-
await adapter
|
|
102
|
+
await adapterCommit(transaction, adapter);
|
|
19
103
|
await transaction.commit();
|
|
20
104
|
}
|
|
21
105
|
catch (error) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (hooks?.onError) {
|
|
106
|
+
markFailed(transaction);
|
|
107
|
+
emit(TRANSACTION_EVENTS.FAILED, transaction, error);
|
|
108
|
+
if (hooks?.onError)
|
|
26
109
|
await hooks.onError({ transaction, error });
|
|
27
|
-
}
|
|
28
110
|
throw new TransactionCommitError(transaction.id, error);
|
|
29
111
|
}
|
|
30
|
-
|
|
112
|
+
emit(TRANSACTION_EVENTS.COMMITTED, transaction);
|
|
113
|
+
if (hooks?.afterCommit)
|
|
31
114
|
await hooks.afterCommit({ transaction });
|
|
32
|
-
}
|
|
33
115
|
}
|
|
34
116
|
/**
|
|
35
117
|
* Rollback a transaction with hooks and adapter coordination.
|
|
118
|
+
*
|
|
119
|
+
* @throws {TransactionRollbackError} when the adapter refuses the rollback.
|
|
36
120
|
*/
|
|
37
|
-
export async function rollbackTransaction(transaction, adapter, reason, hooks) {
|
|
121
|
+
export async function rollbackTransaction(transaction, adapter, reason, hooks, emit = noopEmitter) {
|
|
122
|
+
if (transaction.kind === "participant") {
|
|
123
|
+
await transaction.rollback(reason);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
38
126
|
if (transaction.state === "committed" ||
|
|
39
127
|
transaction.state === "rolled_back" ||
|
|
40
128
|
transaction.state === "failed") {
|
|
41
129
|
return;
|
|
42
130
|
}
|
|
43
|
-
if (hooks?.beforeRollback)
|
|
131
|
+
if (hooks?.beforeRollback)
|
|
44
132
|
await hooks.beforeRollback({ transaction });
|
|
45
|
-
|
|
46
|
-
const handle = transaction._getHandle();
|
|
133
|
+
emit(TRANSACTION_EVENTS.ROLLING_BACK, transaction, reason);
|
|
47
134
|
try {
|
|
48
|
-
if (
|
|
49
|
-
await
|
|
135
|
+
if (transaction.kind !== "none") {
|
|
136
|
+
await adapterRollback(transaction, adapter, reason);
|
|
50
137
|
}
|
|
51
138
|
await transaction.rollback(reason);
|
|
52
139
|
}
|
|
53
140
|
catch (error) {
|
|
54
|
-
|
|
141
|
+
markFailed(transaction);
|
|
142
|
+
emit(TRANSACTION_EVENTS.FAILED, transaction, error);
|
|
143
|
+
if (hooks?.onError)
|
|
55
144
|
await hooks.onError({ transaction, error });
|
|
56
|
-
}
|
|
57
145
|
throw new TransactionRollbackError(transaction.id, {
|
|
58
146
|
cause: error,
|
|
59
147
|
originalError: reason,
|
|
60
148
|
});
|
|
61
149
|
}
|
|
62
|
-
|
|
150
|
+
emit(TRANSACTION_EVENTS.ROLLED_BACK, transaction, reason);
|
|
151
|
+
if (hooks?.afterRollback)
|
|
63
152
|
await hooks.afterRollback({ transaction });
|
|
64
|
-
}
|
|
65
153
|
}
|
|
66
154
|
//# sourceMappingURL=manager.commit.js.map
|
|
@@ -4,23 +4,48 @@
|
|
|
4
4
|
* @module manager/manager
|
|
5
5
|
*/
|
|
6
6
|
import type { Transaction, TransactionOptions } from "../transactionTypes/transaction.interface.js";
|
|
7
|
-
import type { TransactionAdapter } from "../transactionTypes/transactionAdapter.js";
|
|
8
|
-
import type {
|
|
9
|
-
import type { TransactionHooks } from "../transactionTypes/transactionHooks.js";
|
|
7
|
+
import type { TransactionAdapter, TransactionContext } from "../transactionTypes/transactionAdapter.js";
|
|
8
|
+
import type { TransactionEventHandler, TransactionHooks, TransactionRegistry } from "../transactionTypes/transactionHooks.js";
|
|
10
9
|
/** Options for creating a transaction manager. */
|
|
11
10
|
export interface TransactionManagerOptions {
|
|
12
11
|
readonly adapter: TransactionAdapter;
|
|
13
12
|
readonly context?: TransactionContext;
|
|
14
13
|
readonly hooks?: TransactionHooks;
|
|
14
|
+
/** Optional registry notified as transactions start and finish. */
|
|
15
|
+
readonly registry?: TransactionRegistry;
|
|
16
|
+
/**
|
|
17
|
+
* Optional observer for transaction lifecycle events.
|
|
18
|
+
*
|
|
19
|
+
* Receives a {@link TransactionEvent} for each of `TRANSACTION_EVENTS`.
|
|
20
|
+
* A throwing observer is ignored rather than failing the transaction.
|
|
21
|
+
*/
|
|
22
|
+
readonly onEvent?: TransactionEventHandler;
|
|
15
23
|
}
|
|
16
24
|
/**
|
|
17
25
|
* Create a transaction manager.
|
|
18
26
|
*/
|
|
19
27
|
export declare function createTransactionManager(options: TransactionManagerOptions): {
|
|
28
|
+
/**
|
|
29
|
+
* Begin a transaction, applying the requested propagation mode.
|
|
30
|
+
*
|
|
31
|
+
* With the default `required` propagation inside an existing transaction
|
|
32
|
+
* this returns a participant: a handle that observes the enclosing
|
|
33
|
+
* transaction but never commits it.
|
|
34
|
+
*/
|
|
20
35
|
begin(opts?: TransactionOptions): Promise<Transaction>;
|
|
36
|
+
/**
|
|
37
|
+
* Run a callback inside a transaction, committing or rolling back around it.
|
|
38
|
+
*
|
|
39
|
+
* Only a transaction this call opened is completed here: joining an
|
|
40
|
+
* enclosing transaction must not commit it, and a failure inside a
|
|
41
|
+
* participant marks the enclosing transaction rollback-only instead.
|
|
42
|
+
*/
|
|
21
43
|
run<T>(callback: (transaction: Transaction) => Promise<T>, opts?: TransactionOptions): Promise<T>;
|
|
44
|
+
/** Commit a transaction. Participants and committed transactions are no-ops. */
|
|
22
45
|
commit(transaction: Transaction): Promise<void>;
|
|
46
|
+
/** Roll back a transaction, or mark the joined transaction rollback-only. */
|
|
23
47
|
rollback(transaction: Transaction, reason?: unknown): Promise<void>;
|
|
48
|
+
/** The transaction in scope for the current async execution, if any. */
|
|
24
49
|
getCurrent(): Transaction | undefined;
|
|
25
50
|
};
|
|
26
51
|
//# sourceMappingURL=manager.core.d.ts.map
|