@soulcraft/brainy 5.7.12 → 5.8.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/CHANGELOG.md +20 -7
- package/README.md +6 -2
- package/dist/brainy.d.ts +1 -0
- package/dist/brainy.js +156 -163
- package/dist/graph/graphAdjacencyIndex.d.ts +76 -7
- package/dist/graph/graphAdjacencyIndex.js +94 -9
- package/dist/hnsw/typeAwareHNSWIndex.d.ts +3 -3
- package/dist/hnsw/typeAwareHNSWIndex.js +3 -3
- package/dist/query/typeAwareQueryPlanner.d.ts +4 -4
- package/dist/query/typeAwareQueryPlanner.js +4 -4
- package/dist/transaction/Transaction.d.ts +55 -0
- package/dist/transaction/Transaction.js +175 -0
- package/dist/transaction/TransactionManager.d.ts +67 -0
- package/dist/transaction/TransactionManager.js +145 -0
- package/dist/transaction/errors.d.ts +41 -0
- package/dist/transaction/errors.js +66 -0
- package/dist/transaction/index.d.ts +14 -0
- package/dist/transaction/index.js +14 -0
- package/dist/transaction/operations/IndexOperations.d.ts +172 -0
- package/dist/transaction/operations/IndexOperations.js +301 -0
- package/dist/transaction/operations/StorageOperations.d.ts +128 -0
- package/dist/transaction/operations/StorageOperations.js +253 -0
- package/dist/transaction/operations/index.d.ts +10 -0
- package/dist/transaction/operations/index.js +13 -0
- package/dist/transaction/types.d.ts +84 -0
- package/dist/transaction/types.js +8 -0
- package/package.json +1 -1
|
@@ -87,11 +87,39 @@ export class GraphAdjacencyIndex {
|
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
89
|
* Core API - Neighbor lookup with LSM-tree storage
|
|
90
|
-
*
|
|
90
|
+
*
|
|
91
|
+
* O(log n) with bloom filter optimization (90% of queries skip disk I/O)
|
|
92
|
+
* v5.8.0: Added pagination support for high-degree nodes
|
|
93
|
+
*
|
|
94
|
+
* @param id Entity ID to get neighbors for
|
|
95
|
+
* @param optionsOrDirection Optional: direction string OR options object
|
|
96
|
+
* @returns Array of neighbor IDs (paginated if limit/offset specified)
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Get all neighbors (backward compatible)
|
|
100
|
+
* const all = await graphIndex.getNeighbors(id)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // Get outgoing neighbors (backward compatible)
|
|
104
|
+
* const out = await graphIndex.getNeighbors(id, 'out')
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Get first 50 outgoing neighbors (new API)
|
|
108
|
+
* const page1 = await graphIndex.getNeighbors(id, { direction: 'out', limit: 50 })
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* // Paginate through neighbors
|
|
112
|
+
* const page1 = await graphIndex.getNeighbors(id, { limit: 100, offset: 0 })
|
|
113
|
+
* const page2 = await graphIndex.getNeighbors(id, { limit: 100, offset: 100 })
|
|
91
114
|
*/
|
|
92
|
-
async getNeighbors(id,
|
|
115
|
+
async getNeighbors(id, optionsOrDirection) {
|
|
93
116
|
await this.ensureInitialized();
|
|
117
|
+
// Normalize old API (direction string) to new API (options object)
|
|
118
|
+
const options = typeof optionsOrDirection === 'string'
|
|
119
|
+
? { direction: optionsOrDirection }
|
|
120
|
+
: (optionsOrDirection || {});
|
|
94
121
|
const startTime = performance.now();
|
|
122
|
+
const direction = options.direction || 'both';
|
|
95
123
|
const neighbors = new Set();
|
|
96
124
|
// Query LSM-trees with bloom filter optimization
|
|
97
125
|
if (direction !== 'in') {
|
|
@@ -106,7 +134,14 @@ export class GraphAdjacencyIndex {
|
|
|
106
134
|
incoming.forEach(neighborId => neighbors.add(neighborId));
|
|
107
135
|
}
|
|
108
136
|
}
|
|
109
|
-
|
|
137
|
+
// Convert to array for pagination
|
|
138
|
+
let result = Array.from(neighbors);
|
|
139
|
+
// Apply pagination if requested (v5.8.0)
|
|
140
|
+
if (options?.limit !== undefined || options?.offset !== undefined) {
|
|
141
|
+
const offset = options.offset || 0;
|
|
142
|
+
const limit = options.limit !== undefined ? options.limit : result.length;
|
|
143
|
+
result = result.slice(offset, offset + limit);
|
|
144
|
+
}
|
|
110
145
|
const elapsed = performance.now() - startTime;
|
|
111
146
|
// Performance assertion - should be sub-5ms with LSM-tree
|
|
112
147
|
if (elapsed > 5.0) {
|
|
@@ -116,13 +151,31 @@ export class GraphAdjacencyIndex {
|
|
|
116
151
|
}
|
|
117
152
|
/**
|
|
118
153
|
* Get verb IDs by source - Billion-scale optimization for getVerbsBySource
|
|
154
|
+
*
|
|
119
155
|
* O(log n) LSM-tree lookup with bloom filter optimization
|
|
120
156
|
* v5.7.1: Filters out deleted verb IDs (tombstone deletion workaround)
|
|
157
|
+
* v5.8.0: Added pagination support for entities with many relationships
|
|
121
158
|
*
|
|
122
159
|
* @param sourceId Source entity ID
|
|
123
|
-
* @
|
|
160
|
+
* @param options Optional configuration
|
|
161
|
+
* @param options.limit Maximum number of verb IDs to return (default: all)
|
|
162
|
+
* @param options.offset Number of verb IDs to skip (default: 0)
|
|
163
|
+
* @returns Array of verb IDs originating from this source (excluding deleted, paginated if requested)
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* // Get all verb IDs (backward compatible)
|
|
167
|
+
* const all = await graphIndex.getVerbIdsBySource(sourceId)
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Get first 50 verb IDs
|
|
171
|
+
* const page1 = await graphIndex.getVerbIdsBySource(sourceId, { limit: 50 })
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* // Paginate through verb IDs
|
|
175
|
+
* const page1 = await graphIndex.getVerbIdsBySource(sourceId, { limit: 100, offset: 0 })
|
|
176
|
+
* const page2 = await graphIndex.getVerbIdsBySource(sourceId, { limit: 100, offset: 100 })
|
|
124
177
|
*/
|
|
125
|
-
async getVerbIdsBySource(sourceId) {
|
|
178
|
+
async getVerbIdsBySource(sourceId, options) {
|
|
126
179
|
await this.ensureInitialized();
|
|
127
180
|
const startTime = performance.now();
|
|
128
181
|
const verbIds = await this.lsmTreeVerbsBySource.get(sourceId);
|
|
@@ -134,17 +187,42 @@ export class GraphAdjacencyIndex {
|
|
|
134
187
|
// Filter out deleted verb IDs (tombstone deletion workaround)
|
|
135
188
|
// LSM-tree retains all IDs, but verbIdSet tracks deletions
|
|
136
189
|
const allIds = verbIds || [];
|
|
137
|
-
|
|
190
|
+
let result = allIds.filter(id => this.verbIdSet.has(id));
|
|
191
|
+
// Apply pagination if requested (v5.8.0)
|
|
192
|
+
if (options?.limit !== undefined || options?.offset !== undefined) {
|
|
193
|
+
const offset = options.offset || 0;
|
|
194
|
+
const limit = options.limit !== undefined ? options.limit : result.length;
|
|
195
|
+
result = result.slice(offset, offset + limit);
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
138
198
|
}
|
|
139
199
|
/**
|
|
140
200
|
* Get verb IDs by target - Billion-scale optimization for getVerbsByTarget
|
|
201
|
+
*
|
|
141
202
|
* O(log n) LSM-tree lookup with bloom filter optimization
|
|
142
203
|
* v5.7.1: Filters out deleted verb IDs (tombstone deletion workaround)
|
|
204
|
+
* v5.8.0: Added pagination support for popular target entities
|
|
143
205
|
*
|
|
144
206
|
* @param targetId Target entity ID
|
|
145
|
-
* @
|
|
207
|
+
* @param options Optional configuration
|
|
208
|
+
* @param options.limit Maximum number of verb IDs to return (default: all)
|
|
209
|
+
* @param options.offset Number of verb IDs to skip (default: 0)
|
|
210
|
+
* @returns Array of verb IDs pointing to this target (excluding deleted, paginated if requested)
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Get all verb IDs (backward compatible)
|
|
214
|
+
* const all = await graphIndex.getVerbIdsByTarget(targetId)
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // Get first 50 verb IDs
|
|
218
|
+
* const page1 = await graphIndex.getVerbIdsByTarget(targetId, { limit: 50 })
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* // Paginate through verb IDs
|
|
222
|
+
* const page1 = await graphIndex.getVerbIdsByTarget(targetId, { limit: 100, offset: 0 })
|
|
223
|
+
* const page2 = await graphIndex.getVerbIdsByTarget(targetId, { limit: 100, offset: 100 })
|
|
146
224
|
*/
|
|
147
|
-
async getVerbIdsByTarget(targetId) {
|
|
225
|
+
async getVerbIdsByTarget(targetId, options) {
|
|
148
226
|
await this.ensureInitialized();
|
|
149
227
|
const startTime = performance.now();
|
|
150
228
|
const verbIds = await this.lsmTreeVerbsByTarget.get(targetId);
|
|
@@ -156,7 +234,14 @@ export class GraphAdjacencyIndex {
|
|
|
156
234
|
// Filter out deleted verb IDs (tombstone deletion workaround)
|
|
157
235
|
// LSM-tree retains all IDs, but verbIdSet tracks deletions
|
|
158
236
|
const allIds = verbIds || [];
|
|
159
|
-
|
|
237
|
+
let result = allIds.filter(id => this.verbIdSet.has(id));
|
|
238
|
+
// Apply pagination if requested (v5.8.0)
|
|
239
|
+
if (options?.limit !== undefined || options?.offset !== undefined) {
|
|
240
|
+
const offset = options.offset || 0;
|
|
241
|
+
const limit = options.limit !== undefined ? options.limit : result.length;
|
|
242
|
+
result = result.slice(offset, offset + limit);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
160
245
|
}
|
|
161
246
|
/**
|
|
162
247
|
* Get verb from cache or storage - Billion-scale memory optimization
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Type-Aware HNSW Index - Phase 2 Billion-Scale Optimization
|
|
3
3
|
*
|
|
4
4
|
* Maintains separate HNSW graphs per entity type for massive memory savings:
|
|
5
|
-
* - Memory @ 1B scale: 384GB → 50GB (-87%)
|
|
6
|
-
* - Query speed: 10x faster for single-type queries
|
|
5
|
+
* - Memory @ 1B scale: PROJECTED 384GB → 50GB (-87% from architectural analysis, not yet benchmarked)
|
|
6
|
+
* - Query speed: PROJECTED 10x faster for single-type queries (not yet benchmarked)
|
|
7
7
|
* - Storage: Already type-first from Phase 1a
|
|
8
8
|
*
|
|
9
9
|
* Architecture:
|
|
@@ -35,7 +35,7 @@ export interface TypeAwareHNSWStats {
|
|
|
35
35
|
* TypeAwareHNSWIndex - Separate HNSW graphs per entity type
|
|
36
36
|
*
|
|
37
37
|
* Phase 2 of billion-scale optimization roadmap.
|
|
38
|
-
* Reduces HNSW memory by 87% @ billion scale.
|
|
38
|
+
* PROJECTED: Reduces HNSW memory by 87% @ billion scale (calculated from architecture, not yet benchmarked).
|
|
39
39
|
*/
|
|
40
40
|
export declare class TypeAwareHNSWIndex {
|
|
41
41
|
private indexes;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Type-Aware HNSW Index - Phase 2 Billion-Scale Optimization
|
|
3
3
|
*
|
|
4
4
|
* Maintains separate HNSW graphs per entity type for massive memory savings:
|
|
5
|
-
* - Memory @ 1B scale: 384GB → 50GB (-87%)
|
|
6
|
-
* - Query speed: 10x faster for single-type queries
|
|
5
|
+
* - Memory @ 1B scale: PROJECTED 384GB → 50GB (-87% from architectural analysis, not yet benchmarked)
|
|
6
|
+
* - Query speed: PROJECTED 10x faster for single-type queries (not yet benchmarked)
|
|
7
7
|
* - Storage: Already type-first from Phase 1a
|
|
8
8
|
*
|
|
9
9
|
* Architecture:
|
|
@@ -27,7 +27,7 @@ const DEFAULT_CONFIG = {
|
|
|
27
27
|
* TypeAwareHNSWIndex - Separate HNSW graphs per entity type
|
|
28
28
|
*
|
|
29
29
|
* Phase 2 of billion-scale optimization roadmap.
|
|
30
|
-
* Reduces HNSW memory by 87% @ billion scale.
|
|
30
|
+
* PROJECTED: Reduces HNSW memory by 87% @ billion scale (calculated from architecture, not yet benchmarked).
|
|
31
31
|
*/
|
|
32
32
|
export class TypeAwareHNSWIndex {
|
|
33
33
|
/**
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* natural language queries using semantic similarity and routing to specific
|
|
6
6
|
* TypeAwareHNSWIndex graphs.
|
|
7
7
|
*
|
|
8
|
-
* Performance Impact:
|
|
8
|
+
* Performance Impact (PROJECTED - not yet benchmarked):
|
|
9
9
|
* - Single-type queries: 42x speedup (search 1/42 graphs)
|
|
10
10
|
* - Multi-type queries: 8-21x speedup (search 2-5/42 graphs)
|
|
11
|
-
* - Overall: 40% latency reduction @ 1B scale
|
|
11
|
+
* - Overall: PROJECTED 40% latency reduction @ 1B scale (calculated from graph reduction, not measured)
|
|
12
12
|
*
|
|
13
13
|
* Examples:
|
|
14
|
-
* - "Find engineers" → single-type → [Person] → 42x speedup
|
|
15
|
-
* - "People at Tesla" → multi-type → [Person, Organization] → 21x speedup
|
|
14
|
+
* - "Find engineers" → single-type → [Person] → PROJECTED 42x speedup
|
|
15
|
+
* - "People at Tesla" → multi-type → [Person, Organization] → PROJECTED 21x speedup
|
|
16
16
|
* - "Everything about AI" → all-types → [all 42 types] → no speedup
|
|
17
17
|
*/
|
|
18
18
|
import { NounType } from '../types/graphTypes.js';
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* natural language queries using semantic similarity and routing to specific
|
|
6
6
|
* TypeAwareHNSWIndex graphs.
|
|
7
7
|
*
|
|
8
|
-
* Performance Impact:
|
|
8
|
+
* Performance Impact (PROJECTED - not yet benchmarked):
|
|
9
9
|
* - Single-type queries: 42x speedup (search 1/42 graphs)
|
|
10
10
|
* - Multi-type queries: 8-21x speedup (search 2-5/42 graphs)
|
|
11
|
-
* - Overall: 40% latency reduction @ 1B scale
|
|
11
|
+
* - Overall: PROJECTED 40% latency reduction @ 1B scale (calculated from graph reduction, not measured)
|
|
12
12
|
*
|
|
13
13
|
* Examples:
|
|
14
|
-
* - "Find engineers" → single-type → [Person] → 42x speedup
|
|
15
|
-
* - "People at Tesla" → multi-type → [Person, Organization] → 21x speedup
|
|
14
|
+
* - "Find engineers" → single-type → [Person] → PROJECTED 42x speedup
|
|
15
|
+
* - "People at Tesla" → multi-type → [Person, Organization] → PROJECTED 21x speedup
|
|
16
16
|
* - "Everything about AI" → all-types → [all 42 types] → no speedup
|
|
17
17
|
*/
|
|
18
18
|
import { NounType, NOUN_TYPE_COUNT } from '../types/graphTypes.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction - Atomic Unit of Work
|
|
3
|
+
*
|
|
4
|
+
* Executes operations atomically: all succeed or all rollback.
|
|
5
|
+
* Prevents partial failures that leave system in inconsistent state.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const tx = new Transaction()
|
|
10
|
+
* tx.addOperation(operation1)
|
|
11
|
+
* tx.addOperation(operation2)
|
|
12
|
+
* await tx.execute() // Both succeed or both rollback
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { Operation, TransactionState, TransactionContext, TransactionOptions } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Transaction class
|
|
18
|
+
*/
|
|
19
|
+
export declare class Transaction implements TransactionContext {
|
|
20
|
+
private operations;
|
|
21
|
+
private rollbackActions;
|
|
22
|
+
private state;
|
|
23
|
+
private readonly options;
|
|
24
|
+
private startTime?;
|
|
25
|
+
private endTime?;
|
|
26
|
+
constructor(options?: TransactionOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Add an operation to the transaction
|
|
29
|
+
*/
|
|
30
|
+
addOperation(operation: Operation): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get current transaction state
|
|
33
|
+
*/
|
|
34
|
+
getState(): TransactionState;
|
|
35
|
+
/**
|
|
36
|
+
* Get number of operations in transaction
|
|
37
|
+
*/
|
|
38
|
+
getOperationCount(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Execute all operations atomically
|
|
41
|
+
*/
|
|
42
|
+
execute(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Commit the transaction
|
|
45
|
+
*/
|
|
46
|
+
private commit;
|
|
47
|
+
/**
|
|
48
|
+
* Rollback all executed operations in reverse order
|
|
49
|
+
*/
|
|
50
|
+
private rollback;
|
|
51
|
+
/**
|
|
52
|
+
* Get transaction execution time in milliseconds
|
|
53
|
+
*/
|
|
54
|
+
getExecutionTimeMs(): number | undefined;
|
|
55
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction - Atomic Unit of Work
|
|
3
|
+
*
|
|
4
|
+
* Executes operations atomically: all succeed or all rollback.
|
|
5
|
+
* Prevents partial failures that leave system in inconsistent state.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const tx = new Transaction()
|
|
10
|
+
* tx.addOperation(operation1)
|
|
11
|
+
* tx.addOperation(operation2)
|
|
12
|
+
* await tx.execute() // Both succeed or both rollback
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { InvalidTransactionStateError, TransactionExecutionError, TransactionRollbackError, TransactionTimeoutError } from './errors.js';
|
|
16
|
+
import { prodLog } from '../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Default transaction options
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_OPTIONS = {
|
|
21
|
+
timeout: 30000, // 30 seconds
|
|
22
|
+
logging: false,
|
|
23
|
+
maxRollbackRetries: 3
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Transaction class
|
|
27
|
+
*/
|
|
28
|
+
export class Transaction {
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.operations = [];
|
|
31
|
+
this.rollbackActions = [];
|
|
32
|
+
this.state = 'pending';
|
|
33
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Add an operation to the transaction
|
|
37
|
+
*/
|
|
38
|
+
addOperation(operation) {
|
|
39
|
+
if (this.state !== 'pending') {
|
|
40
|
+
throw new InvalidTransactionStateError(this.state, 'add operation');
|
|
41
|
+
}
|
|
42
|
+
this.operations.push(operation);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get current transaction state
|
|
46
|
+
*/
|
|
47
|
+
getState() {
|
|
48
|
+
return this.state;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get number of operations in transaction
|
|
52
|
+
*/
|
|
53
|
+
getOperationCount() {
|
|
54
|
+
return this.operations.length;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute all operations atomically
|
|
58
|
+
*/
|
|
59
|
+
async execute() {
|
|
60
|
+
if (this.state !== 'pending') {
|
|
61
|
+
throw new InvalidTransactionStateError(this.state, 'execute');
|
|
62
|
+
}
|
|
63
|
+
this.state = 'executing';
|
|
64
|
+
this.startTime = Date.now();
|
|
65
|
+
if (this.options.logging) {
|
|
66
|
+
prodLog.info(`[Transaction] Executing ${this.operations.length} operations`);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Execute each operation in order
|
|
70
|
+
for (let i = 0; i < this.operations.length; i++) {
|
|
71
|
+
// Check timeout
|
|
72
|
+
if (Date.now() - this.startTime > this.options.timeout) {
|
|
73
|
+
throw new TransactionTimeoutError(this.options.timeout, i);
|
|
74
|
+
}
|
|
75
|
+
const operation = this.operations[i];
|
|
76
|
+
try {
|
|
77
|
+
if (this.options.logging) {
|
|
78
|
+
prodLog.info(`[Transaction] Executing operation ${i}: ${operation.name || 'unnamed'}`);
|
|
79
|
+
}
|
|
80
|
+
// Execute operation
|
|
81
|
+
const rollbackAction = await operation.execute();
|
|
82
|
+
// Record rollback action (if provided)
|
|
83
|
+
if (rollbackAction) {
|
|
84
|
+
this.rollbackActions.push(rollbackAction);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// Operation failed - rollback and re-throw
|
|
89
|
+
const executionError = new TransactionExecutionError(`Operation ${i} failed: ${error.message}`, i, operation.name, error);
|
|
90
|
+
await this.rollback(executionError);
|
|
91
|
+
throw executionError;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// All operations succeeded - commit
|
|
95
|
+
this.commit();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// Error already handled in rollback
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Commit the transaction
|
|
104
|
+
*/
|
|
105
|
+
commit() {
|
|
106
|
+
this.state = 'committed';
|
|
107
|
+
this.endTime = Date.now();
|
|
108
|
+
if (this.options.logging) {
|
|
109
|
+
const duration = this.endTime - (this.startTime || this.endTime);
|
|
110
|
+
prodLog.info(`[Transaction] Committed successfully in ${duration}ms`);
|
|
111
|
+
}
|
|
112
|
+
// Clear rollback actions - no longer needed
|
|
113
|
+
this.rollbackActions = [];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Rollback all executed operations in reverse order
|
|
117
|
+
*/
|
|
118
|
+
async rollback(originalError) {
|
|
119
|
+
this.state = 'rolling_back';
|
|
120
|
+
if (this.options.logging) {
|
|
121
|
+
prodLog.info(`[Transaction] Rolling back ${this.rollbackActions.length} operations`);
|
|
122
|
+
}
|
|
123
|
+
const rollbackErrors = [];
|
|
124
|
+
// Execute rollback actions in REVERSE order
|
|
125
|
+
for (let i = this.rollbackActions.length - 1; i >= 0; i--) {
|
|
126
|
+
const action = this.rollbackActions[i];
|
|
127
|
+
// Retry rollback with exponential backoff
|
|
128
|
+
let attempts = 0;
|
|
129
|
+
let success = false;
|
|
130
|
+
while (attempts < this.options.maxRollbackRetries && !success) {
|
|
131
|
+
try {
|
|
132
|
+
await action();
|
|
133
|
+
success = true;
|
|
134
|
+
if (this.options.logging) {
|
|
135
|
+
prodLog.info(`[Transaction] Rolled back operation ${i}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
attempts++;
|
|
140
|
+
if (attempts >= this.options.maxRollbackRetries) {
|
|
141
|
+
// Max retries exceeded - log error and continue
|
|
142
|
+
const rollbackError = error;
|
|
143
|
+
rollbackErrors.push(rollbackError);
|
|
144
|
+
prodLog.error(`[Transaction] Rollback failed for operation ${i} after ${attempts} attempts: ${rollbackError.message}`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Retry with exponential backoff
|
|
148
|
+
const delayMs = Math.pow(2, attempts) * 100;
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.state = 'rolled_back';
|
|
155
|
+
this.endTime = Date.now();
|
|
156
|
+
if (this.options.logging) {
|
|
157
|
+
const duration = this.endTime - (this.startTime || this.endTime);
|
|
158
|
+
prodLog.info(`[Transaction] Rolled back in ${duration}ms`);
|
|
159
|
+
}
|
|
160
|
+
// If rollback encountered errors, wrap them with original error
|
|
161
|
+
if (rollbackErrors.length > 0) {
|
|
162
|
+
throw new TransactionRollbackError(`Transaction rollback encountered ${rollbackErrors.length} errors during cleanup`, originalError, rollbackErrors);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get transaction execution time in milliseconds
|
|
167
|
+
*/
|
|
168
|
+
getExecutionTimeMs() {
|
|
169
|
+
if (this.startTime && this.endTime) {
|
|
170
|
+
return this.endTime - this.startTime;
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=Transaction.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages transaction execution across the system.
|
|
5
|
+
* Provides high-level API for executing atomic operations.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const txManager = new TransactionManager()
|
|
10
|
+
*
|
|
11
|
+
* const result = await txManager.executeTransaction(async (tx) => {
|
|
12
|
+
* tx.addOperation(operation1)
|
|
13
|
+
* tx.addOperation(operation2)
|
|
14
|
+
* return someValue
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { TransactionFunction, TransactionResult, TransactionOptions } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Transaction Manager Statistics
|
|
21
|
+
*/
|
|
22
|
+
export interface TransactionStats {
|
|
23
|
+
totalTransactions: number;
|
|
24
|
+
successfulTransactions: number;
|
|
25
|
+
failedTransactions: number;
|
|
26
|
+
rolledBackTransactions: number;
|
|
27
|
+
averageExecutionTimeMs: number;
|
|
28
|
+
averageOperationsPerTransaction: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Transaction Manager
|
|
32
|
+
*/
|
|
33
|
+
export declare class TransactionManager {
|
|
34
|
+
private stats;
|
|
35
|
+
private totalExecutionTime;
|
|
36
|
+
private totalOperations;
|
|
37
|
+
/**
|
|
38
|
+
* Execute a function within a transaction
|
|
39
|
+
* All operations succeed atomically or all rollback
|
|
40
|
+
*
|
|
41
|
+
* @param fn Function that builds and executes transaction
|
|
42
|
+
* @param options Transaction execution options
|
|
43
|
+
* @returns Result from user function
|
|
44
|
+
* @throws TransactionError if transaction fails
|
|
45
|
+
*/
|
|
46
|
+
executeTransaction<T>(fn: TransactionFunction<T>, options?: TransactionOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a transaction and return detailed result
|
|
49
|
+
*/
|
|
50
|
+
executeTransactionWithResult<T>(fn: TransactionFunction<T>, options?: TransactionOptions): Promise<TransactionResult<T>>;
|
|
51
|
+
/**
|
|
52
|
+
* Get transaction statistics
|
|
53
|
+
*/
|
|
54
|
+
getStats(): Readonly<TransactionStats>;
|
|
55
|
+
/**
|
|
56
|
+
* Reset transaction statistics
|
|
57
|
+
*/
|
|
58
|
+
resetStats(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Update running statistics
|
|
61
|
+
*/
|
|
62
|
+
private updateStats;
|
|
63
|
+
/**
|
|
64
|
+
* Log current statistics
|
|
65
|
+
*/
|
|
66
|
+
logStats(): void;
|
|
67
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages transaction execution across the system.
|
|
5
|
+
* Provides high-level API for executing atomic operations.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const txManager = new TransactionManager()
|
|
10
|
+
*
|
|
11
|
+
* const result = await txManager.executeTransaction(async (tx) => {
|
|
12
|
+
* tx.addOperation(operation1)
|
|
13
|
+
* tx.addOperation(operation2)
|
|
14
|
+
* return someValue
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { Transaction } from './Transaction.js';
|
|
19
|
+
import { TransactionError } from './errors.js';
|
|
20
|
+
import { prodLog } from '../utils/logger.js';
|
|
21
|
+
/**
|
|
22
|
+
* Transaction Manager
|
|
23
|
+
*/
|
|
24
|
+
export class TransactionManager {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.stats = {
|
|
27
|
+
totalTransactions: 0,
|
|
28
|
+
successfulTransactions: 0,
|
|
29
|
+
failedTransactions: 0,
|
|
30
|
+
rolledBackTransactions: 0,
|
|
31
|
+
averageExecutionTimeMs: 0,
|
|
32
|
+
averageOperationsPerTransaction: 0
|
|
33
|
+
};
|
|
34
|
+
this.totalExecutionTime = 0;
|
|
35
|
+
this.totalOperations = 0;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Execute a function within a transaction
|
|
39
|
+
* All operations succeed atomically or all rollback
|
|
40
|
+
*
|
|
41
|
+
* @param fn Function that builds and executes transaction
|
|
42
|
+
* @param options Transaction execution options
|
|
43
|
+
* @returns Result from user function
|
|
44
|
+
* @throws TransactionError if transaction fails
|
|
45
|
+
*/
|
|
46
|
+
async executeTransaction(fn, options) {
|
|
47
|
+
const transaction = new Transaction(options);
|
|
48
|
+
this.stats.totalTransactions++;
|
|
49
|
+
try {
|
|
50
|
+
// Execute user function (builds operations list)
|
|
51
|
+
const result = await fn(transaction);
|
|
52
|
+
// Execute all operations atomically
|
|
53
|
+
await transaction.execute();
|
|
54
|
+
// Update statistics
|
|
55
|
+
this.stats.successfulTransactions++;
|
|
56
|
+
this.updateStats(transaction);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Transaction failed and rolled back
|
|
61
|
+
this.stats.failedTransactions++;
|
|
62
|
+
if (transaction.getState() === 'rolled_back') {
|
|
63
|
+
this.stats.rolledBackTransactions++;
|
|
64
|
+
}
|
|
65
|
+
this.updateStats(transaction);
|
|
66
|
+
// Re-throw with context
|
|
67
|
+
if (error instanceof TransactionError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
throw new TransactionError(`Transaction failed: ${error.message}`, { cause: error });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute a transaction and return detailed result
|
|
77
|
+
*/
|
|
78
|
+
async executeTransactionWithResult(fn, options) {
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
const transaction = new Transaction(options);
|
|
81
|
+
try {
|
|
82
|
+
const value = await fn(transaction);
|
|
83
|
+
await transaction.execute();
|
|
84
|
+
const executionTimeMs = Date.now() - startTime;
|
|
85
|
+
return {
|
|
86
|
+
value,
|
|
87
|
+
operationCount: transaction.getOperationCount(),
|
|
88
|
+
executionTimeMs
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Transaction failed
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get transaction statistics
|
|
98
|
+
*/
|
|
99
|
+
getStats() {
|
|
100
|
+
return { ...this.stats };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reset transaction statistics
|
|
104
|
+
*/
|
|
105
|
+
resetStats() {
|
|
106
|
+
this.stats = {
|
|
107
|
+
totalTransactions: 0,
|
|
108
|
+
successfulTransactions: 0,
|
|
109
|
+
failedTransactions: 0,
|
|
110
|
+
rolledBackTransactions: 0,
|
|
111
|
+
averageExecutionTimeMs: 0,
|
|
112
|
+
averageOperationsPerTransaction: 0
|
|
113
|
+
};
|
|
114
|
+
this.totalExecutionTime = 0;
|
|
115
|
+
this.totalOperations = 0;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Update running statistics
|
|
119
|
+
*/
|
|
120
|
+
updateStats(transaction) {
|
|
121
|
+
const executionTime = transaction.getExecutionTimeMs();
|
|
122
|
+
const operationCount = transaction.getOperationCount();
|
|
123
|
+
if (executionTime !== undefined) {
|
|
124
|
+
this.totalExecutionTime += executionTime;
|
|
125
|
+
this.stats.averageExecutionTimeMs =
|
|
126
|
+
this.totalExecutionTime / this.stats.totalTransactions;
|
|
127
|
+
}
|
|
128
|
+
this.totalOperations += operationCount;
|
|
129
|
+
this.stats.averageOperationsPerTransaction =
|
|
130
|
+
this.totalOperations / this.stats.totalTransactions;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Log current statistics
|
|
134
|
+
*/
|
|
135
|
+
logStats() {
|
|
136
|
+
prodLog.info('[TransactionManager] Statistics:');
|
|
137
|
+
prodLog.info(` Total: ${this.stats.totalTransactions}`);
|
|
138
|
+
prodLog.info(` Successful: ${this.stats.successfulTransactions}`);
|
|
139
|
+
prodLog.info(` Failed: ${this.stats.failedTransactions}`);
|
|
140
|
+
prodLog.info(` Rolled back: ${this.stats.rolledBackTransactions}`);
|
|
141
|
+
prodLog.info(` Avg execution time: ${this.stats.averageExecutionTimeMs.toFixed(2)}ms`);
|
|
142
|
+
prodLog.info(` Avg operations/tx: ${this.stats.averageOperationsPerTransaction.toFixed(2)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=TransactionManager.js.map
|