BREAKING CHANGE(core): Refactor to v3: introduce modular core/domain architecture, plugin system, observability and strict TypeScript configuration; remove legacy classes

This commit is contained in:
2025-11-29 18:32:00 +00:00
parent 53673e37cb
commit 7e89b6ebf5
68 changed files with 17020 additions and 720 deletions

View File

@@ -0,0 +1,31 @@
/**
* Transaction Module
*
* Distributed transactions with ACID-like semantics
*/
// Main classes
export {
TransactionManager,
Transaction,
createTransactionManager,
} from './transaction-manager.js';
// Types
export type {
TransactionIsolationLevel,
TransactionState,
LockingStrategy,
TransactionOperationType,
TransactionOperation,
TransactionConfig,
TransactionContext,
TransactionResult,
TransactionStats,
LockInfo,
ConflictResolutionStrategy,
ConflictInfo,
TransactionManagerConfig,
Savepoint,
TransactionCallbacks,
} from './types.js';

View File

@@ -0,0 +1,859 @@
/**
* Transaction Manager
*
* Manages distributed transactions with ACID-like semantics
*/
import { ElasticsearchConnectionManager } from '../../core/connection/connection-manager.js';
import { Logger, defaultLogger } from '../../core/observability/logger.js';
import { MetricsCollector, defaultMetricsCollector } from '../../core/observability/metrics.js';
import { DocumentConflictError } from '../../core/errors/index.js';
import type {
TransactionConfig,
TransactionContext,
TransactionOperation,
TransactionResult,
TransactionStats,
TransactionState,
TransactionManagerConfig,
TransactionCallbacks,
ConflictInfo,
ConflictResolutionStrategy,
Savepoint,
} from './types.js';
/**
* Default configuration
*/
const DEFAULT_CONFIG: Required<TransactionManagerConfig> = {
defaultIsolationLevel: 'read_committed',
defaultLockingStrategy: 'optimistic',
defaultTimeout: 30000, // 30 seconds
enableCleanup: true,
cleanupInterval: 60000, // 1 minute
maxConcurrentTransactions: 1000,
conflictResolution: 'retry',
enableLogging: true,
enableMetrics: true,
};
/**
* Transaction Manager
*/
export class TransactionManager {
private config: Required<TransactionManagerConfig>;
private transactions: Map<string, TransactionContext> = new Map();
private stats: TransactionStats;
private cleanupTimer?: NodeJS.Timeout;
private logger: Logger;
private metrics: MetricsCollector;
private transactionCounter = 0;
constructor(config: TransactionManagerConfig = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.logger = defaultLogger;
this.metrics = defaultMetricsCollector;
this.stats = {
totalStarted: 0,
totalCommitted: 0,
totalRolledBack: 0,
totalFailed: 0,
totalOperations: 0,
totalConflicts: 0,
totalRetries: 0,
avgDuration: 0,
avgOperationsPerTransaction: 0,
successRate: 0,
activeTransactions: 0,
};
}
/**
* Initialize transaction manager
*/
async initialize(): Promise<void> {
if (this.config.enableCleanup) {
this.startCleanupTimer();
}
this.logger.info('TransactionManager initialized', {
defaultIsolationLevel: this.config.defaultIsolationLevel,
defaultLockingStrategy: this.config.defaultLockingStrategy,
maxConcurrentTransactions: this.config.maxConcurrentTransactions,
});
}
/**
* Begin a new transaction
*/
async begin(
config: TransactionConfig = {},
callbacks?: TransactionCallbacks
): Promise<Transaction> {
// Check concurrent transaction limit
if (this.transactions.size >= this.config.maxConcurrentTransactions) {
throw new Error(
`Maximum concurrent transactions limit reached (${this.config.maxConcurrentTransactions})`
);
}
// Generate transaction ID
const transactionId = config.id || this.generateTransactionId();
// Create transaction context
const context: TransactionContext = {
id: transactionId,
state: 'active',
config: {
id: transactionId,
isolationLevel: config.isolationLevel ?? this.config.defaultIsolationLevel,
lockingStrategy: config.lockingStrategy ?? this.config.defaultLockingStrategy,
timeout: config.timeout ?? this.config.defaultTimeout,
autoRollback: config.autoRollback ?? true,
maxRetries: config.maxRetries ?? 3,
retryDelay: config.retryDelay ?? 100,
strictOrdering: config.strictOrdering ?? false,
metadata: config.metadata ?? {},
},
operations: [],
readSet: new Map(),
writeSet: new Set(),
startTime: new Date(),
retryAttempts: 0,
};
this.transactions.set(transactionId, context);
this.stats.totalStarted++;
this.stats.activeTransactions++;
if (this.config.enableLogging) {
this.logger.info('Transaction started', {
transactionId,
isolationLevel: context.config.isolationLevel,
lockingStrategy: context.config.lockingStrategy,
});
}
if (this.config.enableMetrics) {
this.metrics.recordCounter('transactions.started', 1);
this.metrics.recordGauge('transactions.active', this.stats.activeTransactions);
}
// Call onBegin callback
if (callbacks?.onBegin) {
await callbacks.onBegin(context);
}
return new Transaction(this, context, callbacks);
}
/**
* Get transaction context
*/
getTransaction(transactionId: string): TransactionContext | undefined {
return this.transactions.get(transactionId);
}
/**
* Commit a transaction
*/
async commit(transactionId: string, callbacks?: TransactionCallbacks): Promise<TransactionResult> {
const context = this.transactions.get(transactionId);
if (!context) {
throw new Error(`Transaction ${transactionId} not found`);
}
if (context.state !== 'active' && context.state !== 'prepared') {
throw new Error(`Cannot commit transaction in state: ${context.state}`);
}
const startTime = Date.now();
try {
// Call onBeforeCommit callback
if (callbacks?.onBeforeCommit) {
await callbacks.onBeforeCommit(context);
}
context.state = 'committing';
const client = ElasticsearchConnectionManager.getInstance().getClient();
// Execute and commit all operations
let committed = 0;
for (const operation of context.operations) {
if (operation.committed) {
committed++;
continue;
}
// Execute operation if not yet executed
if (!operation.executed) {
await this.executeOperation(context, operation, callbacks);
}
// Mark as committed
operation.committed = true;
committed++;
}
context.state = 'committed';
context.endTime = new Date();
const duration = Date.now() - startTime;
this.stats.totalCommitted++;
this.stats.activeTransactions--;
this.updateAverages(duration, context.operations.length);
const result: TransactionResult = {
success: true,
transactionId,
state: 'committed',
operationsExecuted: context.operations.filter((op) => op.executed).length,
operationsCommitted: committed,
operationsRolledBack: 0,
duration,
metadata: context.config.metadata,
};
if (this.config.enableLogging) {
this.logger.info('Transaction committed', {
transactionId,
operations: committed,
duration,
});
}
if (this.config.enableMetrics) {
this.metrics.recordCounter('transactions.committed', 1);
this.metrics.recordHistogram('transactions.duration', duration);
this.metrics.recordGauge('transactions.active', this.stats.activeTransactions);
}
// Call onAfterCommit callback
if (callbacks?.onAfterCommit) {
await callbacks.onAfterCommit(result);
}
// Cleanup transaction
this.transactions.delete(transactionId);
return result;
} catch (error: any) {
context.state = 'failed';
context.error = error;
if (this.config.enableLogging) {
this.logger.error('Transaction commit failed', {
transactionId,
error: error.message,
});
}
// Auto-rollback if enabled
if (context.config.autoRollback) {
return await this.rollback(transactionId, callbacks);
}
throw error;
}
}
/**
* Rollback a transaction
*/
async rollback(
transactionId: string,
callbacks?: TransactionCallbacks
): Promise<TransactionResult> {
const context = this.transactions.get(transactionId);
if (!context) {
throw new Error(`Transaction ${transactionId} not found`);
}
const startTime = Date.now();
try {
// Call onBeforeRollback callback
if (callbacks?.onBeforeRollback) {
await callbacks.onBeforeRollback(context);
}
context.state = 'rolling_back';
const client = ElasticsearchConnectionManager.getInstance().getClient();
// Execute compensation operations in reverse order
let rolledBack = 0;
for (let i = context.operations.length - 1; i >= 0; i--) {
const operation = context.operations[i];
if (!operation.executed || !operation.compensation) {
continue;
}
try {
await this.executeOperation(context, operation.compensation);
rolledBack++;
} catch (error: any) {
this.logger.error('Compensation operation failed', {
transactionId,
operation: operation.type,
index: operation.index,
id: operation.id,
error: error.message,
});
}
}
context.state = 'rolled_back';
context.endTime = new Date();
const duration = Date.now() - startTime;
this.stats.totalRolledBack++;
this.stats.activeTransactions--;
const result: TransactionResult = {
success: false,
transactionId,
state: 'rolled_back',
operationsExecuted: context.operations.filter((op) => op.executed).length,
operationsCommitted: 0,
operationsRolledBack: rolledBack,
duration,
error: context.error
? {
message: context.error.message,
type: context.error.name,
}
: undefined,
metadata: context.config.metadata,
};
if (this.config.enableLogging) {
this.logger.info('Transaction rolled back', {
transactionId,
rolledBack,
duration,
});
}
if (this.config.enableMetrics) {
this.metrics.recordCounter('transactions.rolled_back', 1);
this.metrics.recordGauge('transactions.active', this.stats.activeTransactions);
}
// Call onAfterRollback callback
if (callbacks?.onAfterRollback) {
await callbacks.onAfterRollback(result);
}
// Cleanup transaction
this.transactions.delete(transactionId);
return result;
} catch (error: any) {
context.state = 'failed';
context.error = error;
this.stats.totalFailed++;
if (this.config.enableLogging) {
this.logger.error('Transaction rollback failed', {
transactionId,
error: error.message,
});
}
throw error;
}
}
/**
* Get transaction statistics
*/
getStats(): TransactionStats {
return { ...this.stats };
}
/**
* Destroy transaction manager
*/
async destroy(): Promise<void> {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
// Rollback all active transactions
const activeTransactions = Array.from(this.transactions.keys());
for (const transactionId of activeTransactions) {
try {
await this.rollback(transactionId);
} catch (error) {
// Ignore errors during cleanup
}
}
this.transactions.clear();
}
// ============================================================================
// Internal Methods
// ============================================================================
/**
* Add operation to transaction
*/
addOperation(context: TransactionContext, operation: TransactionOperation): void {
context.operations.push(operation);
this.stats.totalOperations++;
const key = `${operation.index}:${operation.id}`;
if (operation.type === 'read') {
// Add to read set for repeatable read
if (operation.version) {
context.readSet.set(key, operation.version);
}
} else {
// Add to write set
context.writeSet.add(key);
}
}
/**
* Execute an operation
*/
private async executeOperation(
context: TransactionContext,
operation: TransactionOperation,
callbacks?: TransactionCallbacks
): Promise<void> {
// Call onBeforeOperation callback
if (callbacks?.onBeforeOperation) {
await callbacks.onBeforeOperation(operation);
}
const client = ElasticsearchConnectionManager.getInstance().getClient();
try {
switch (operation.type) {
case 'read': {
const result = await client.get({
index: operation.index,
id: operation.id,
});
operation.version = {
seqNo: result._seq_no!,
primaryTerm: result._primary_term!,
};
operation.originalDocument = result._source;
break;
}
case 'create': {
const result = await client.index({
index: operation.index,
id: operation.id,
document: operation.document,
op_type: 'create',
});
operation.version = {
seqNo: result._seq_no,
primaryTerm: result._primary_term,
};
// Create compensation (delete)
operation.compensation = {
type: 'delete',
index: operation.index,
id: operation.id,
timestamp: new Date(),
executed: false,
committed: false,
};
break;
}
case 'update': {
const updateRequest: any = {
index: operation.index,
id: operation.id,
document: operation.document,
};
// Add version for optimistic locking
if (operation.version) {
updateRequest.if_seq_no = operation.version.seqNo;
updateRequest.if_primary_term = operation.version.primaryTerm;
}
const result = await client.index(updateRequest);
operation.version = {
seqNo: result._seq_no,
primaryTerm: result._primary_term,
};
// Create compensation (restore original)
if (operation.originalDocument) {
operation.compensation = {
type: 'update',
index: operation.index,
id: operation.id,
document: operation.originalDocument,
timestamp: new Date(),
executed: false,
committed: false,
};
}
break;
}
case 'delete': {
const deleteRequest: any = {
index: operation.index,
id: operation.id,
};
// Add version for optimistic locking
if (operation.version) {
deleteRequest.if_seq_no = operation.version.seqNo;
deleteRequest.if_primary_term = operation.version.primaryTerm;
}
await client.delete(deleteRequest);
// Create compensation (restore document)
if (operation.originalDocument) {
operation.compensation = {
type: 'create',
index: operation.index,
id: operation.id,
document: operation.originalDocument,
timestamp: new Date(),
executed: false,
committed: false,
};
}
break;
}
}
operation.executed = true;
// Call onAfterOperation callback
if (callbacks?.onAfterOperation) {
await callbacks.onAfterOperation(operation);
}
} catch (error: any) {
// Handle version conflict
if (error.name === 'ResponseError' && error.meta?.statusCode === 409) {
await this.handleConflict(context, operation, error, callbacks);
} else {
throw error;
}
}
}
/**
* Handle version conflict
*/
private async handleConflict(
context: TransactionContext,
operation: TransactionOperation,
error: Error,
callbacks?: TransactionCallbacks
): Promise<void> {
this.stats.totalConflicts++;
const conflict: ConflictInfo = {
operation,
expectedVersion: operation.version,
detectedAt: new Date(),
};
if (this.config.enableMetrics) {
this.metrics.recordCounter('transactions.conflicts', 1);
}
// Call onConflict callback
let strategy: ConflictResolutionStrategy = this.config.conflictResolution;
if (callbacks?.onConflict) {
strategy = await callbacks.onConflict(conflict);
}
switch (strategy) {
case 'abort':
throw new DocumentConflictError(
`Version conflict for ${operation.index}/${operation.id}`,
{ index: operation.index, id: operation.id }
);
case 'retry':
if (context.retryAttempts >= context.config.maxRetries) {
throw new DocumentConflictError(
`Max retries exceeded for ${operation.index}/${operation.id}`,
{ index: operation.index, id: operation.id }
);
}
context.retryAttempts++;
this.stats.totalRetries++;
// Wait before retry
await new Promise((resolve) => setTimeout(resolve, context.config.retryDelay));
// Retry operation
await this.executeOperation(context, operation, callbacks);
break;
case 'skip':
// Skip this operation
operation.executed = false;
break;
case 'force':
// Force update without version check
delete operation.version;
await this.executeOperation(context, operation, callbacks);
break;
case 'merge':
// Not implemented - requires custom merge logic
throw new Error('Merge conflict resolution not implemented');
}
}
/**
* Generate transaction ID
*/
private generateTransactionId(): string {
this.transactionCounter++;
return `txn-${Date.now()}-${this.transactionCounter}`;
}
/**
* Start cleanup timer for expired transactions
*/
private startCleanupTimer(): void {
this.cleanupTimer = setInterval(() => {
this.cleanupExpiredTransactions();
}, this.config.cleanupInterval);
}
/**
* Cleanup expired transactions
*/
private cleanupExpiredTransactions(): void {
const now = Date.now();
for (const [transactionId, context] of this.transactions) {
const elapsed = now - context.startTime.getTime();
if (elapsed > context.config.timeout) {
this.logger.warn('Transaction timeout, rolling back', { transactionId });
this.rollback(transactionId).catch((error) => {
this.logger.error('Failed to rollback expired transaction', {
transactionId,
error,
});
});
}
}
}
/**
* Update average statistics
*/
private updateAverages(duration: number, operations: number): void {
const total = this.stats.totalCommitted + this.stats.totalRolledBack;
this.stats.avgDuration =
(this.stats.avgDuration * (total - 1) + duration) / total;
this.stats.avgOperationsPerTransaction =
(this.stats.avgOperationsPerTransaction * (total - 1) + operations) / total;
this.stats.successRate =
this.stats.totalCommitted / (this.stats.totalCommitted + this.stats.totalRolledBack + this.stats.totalFailed);
}
}
/**
* Transaction class for fluent API
*/
export class Transaction {
private savepoints: Map<string, Savepoint> = new Map();
constructor(
private manager: TransactionManager,
private context: TransactionContext,
private callbacks?: TransactionCallbacks
) {}
/**
* Get transaction ID
*/
getId(): string {
return this.context.id;
}
/**
* Get transaction state
*/
getState(): TransactionState {
return this.context.state;
}
/**
* Read a document
*/
async read<T>(index: string, id: string): Promise<T | null> {
const operation: TransactionOperation<T> = {
type: 'read',
index,
id,
timestamp: new Date(),
executed: false,
committed: false,
};
this.manager.addOperation(this.context, operation);
const client = ElasticsearchConnectionManager.getInstance().getClient();
try {
const result = await client.get({ index, id });
operation.version = {
seqNo: result._seq_no!,
primaryTerm: result._primary_term!,
};
operation.originalDocument = result._source as T;
operation.executed = true;
return result._source as T;
} catch (error: any) {
if (error.name === 'ResponseError' && error.meta?.statusCode === 404) {
return null;
}
throw error;
}
}
/**
* Create a document
*/
async create<T>(index: string, id: string, document: T): Promise<void> {
const operation: TransactionOperation<T> = {
type: 'create',
index,
id,
document,
timestamp: new Date(),
executed: false,
committed: false,
};
this.manager.addOperation(this.context, operation);
}
/**
* Update a document
*/
async update<T>(index: string, id: string, document: Partial<T>): Promise<void> {
// First read the current version
const current = await this.read<T>(index, id);
const operation: TransactionOperation<T> = {
type: 'update',
index,
id,
document: { ...current, ...document } as T,
originalDocument: current ?? undefined,
timestamp: new Date(),
executed: false,
committed: false,
};
this.manager.addOperation(this.context, operation);
}
/**
* Delete a document
*/
async delete(index: string, id: string): Promise<void> {
// First read the current version
const current = await this.read(index, id);
const operation: TransactionOperation = {
type: 'delete',
index,
id,
originalDocument: current ?? undefined,
timestamp: new Date(),
executed: false,
committed: false,
};
this.manager.addOperation(this.context, operation);
}
/**
* Create a savepoint
*/
savepoint(name: string): void {
this.savepoints.set(name, {
name,
operationsCount: this.context.operations.length,
createdAt: new Date(),
});
}
/**
* Rollback to savepoint
*/
rollbackTo(name: string): void {
const savepoint = this.savepoints.get(name);
if (!savepoint) {
throw new Error(`Savepoint '${name}' not found`);
}
// Remove operations after savepoint
this.context.operations.splice(savepoint.operationsCount);
}
/**
* Commit the transaction
*/
async commit(): Promise<TransactionResult> {
return await this.manager.commit(this.context.id, this.callbacks);
}
/**
* Rollback the transaction
*/
async rollback(): Promise<TransactionResult> {
return await this.manager.rollback(this.context.id, this.callbacks);
}
}
/**
* Create a transaction manager
*/
export function createTransactionManager(
config?: TransactionManagerConfig
): TransactionManager {
return new TransactionManager(config);
}

View File

@@ -0,0 +1,361 @@
/**
* Transaction types for distributed ACID-like operations
*
* Note: Elasticsearch doesn't natively support ACID transactions across multiple
* documents. This implementation provides transaction-like semantics using:
* - Optimistic concurrency control (seq_no/primary_term)
* - Two-phase operations (prepare/commit)
* - Compensation-based rollback
* - Transaction state tracking
*/
/**
* Transaction isolation level
*/
export type TransactionIsolationLevel =
| 'read_uncommitted'
| 'read_committed'
| 'repeatable_read'
| 'serializable';
/**
* Transaction state
*/
export type TransactionState =
| 'active'
| 'preparing'
| 'prepared'
| 'committing'
| 'committed'
| 'rolling_back'
| 'rolled_back'
| 'failed';
/**
* Transaction locking strategy
*/
export type LockingStrategy = 'optimistic' | 'pessimistic';
/**
* Transaction operation type
*/
export type TransactionOperationType = 'read' | 'create' | 'update' | 'delete';
/**
* Transaction operation
*/
export interface TransactionOperation<T = unknown> {
/** Operation type */
type: TransactionOperationType;
/** Target index */
index: string;
/** Document ID */
id: string;
/** Document data (for create/update) */
document?: T;
/** Original document (for rollback) */
originalDocument?: T;
/** Version info for optimistic locking */
version?: {
seqNo: number;
primaryTerm: number;
};
/** Timestamp when operation was added */
timestamp: Date;
/** Whether operation has been executed */
executed: boolean;
/** Whether operation has been committed */
committed: boolean;
/** Compensation operation for rollback */
compensation?: TransactionOperation;
}
/**
* Transaction configuration
*/
export interface TransactionConfig {
/** Transaction ID (auto-generated if not provided) */
id?: string;
/** Isolation level */
isolationLevel?: TransactionIsolationLevel;
/** Locking strategy */
lockingStrategy?: LockingStrategy;
/** Transaction timeout in milliseconds */
timeout?: number;
/** Enable automatic rollback on error */
autoRollback?: boolean;
/** Maximum retry attempts for conflicts */
maxRetries?: number;
/** Retry delay in milliseconds */
retryDelay?: number;
/** Enable strict ordering of operations */
strictOrdering?: boolean;
/** Metadata for tracking */
metadata?: Record<string, unknown>;
}
/**
* Transaction context
*/
export interface TransactionContext {
/** Transaction ID */
id: string;
/** Current state */
state: TransactionState;
/** Configuration */
config: Required<TransactionConfig>;
/** Operations in this transaction */
operations: TransactionOperation[];
/** Read set (for repeatable read isolation) */
readSet: Map<string, { seqNo: number; primaryTerm: number }>;
/** Write set (for conflict detection) */
writeSet: Set<string>;
/** Transaction start time */
startTime: Date;
/** Transaction end time */
endTime?: Date;
/** Error if transaction failed */
error?: Error;
/** Number of retry attempts */
retryAttempts: number;
}
/**
* Transaction result
*/
export interface TransactionResult {
/** Whether transaction succeeded */
success: boolean;
/** Transaction ID */
transactionId: string;
/** Final state */
state: TransactionState;
/** Number of operations executed */
operationsExecuted: number;
/** Number of operations committed */
operationsCommitted: number;
/** Number of operations rolled back */
operationsRolledBack: number;
/** Transaction duration in milliseconds */
duration: number;
/** Error if transaction failed */
error?: {
message: string;
type: string;
operation?: TransactionOperation;
};
/** Metadata */
metadata?: Record<string, unknown>;
}
/**
* Transaction statistics
*/
export interface TransactionStats {
/** Total transactions started */
totalStarted: number;
/** Total transactions committed */
totalCommitted: number;
/** Total transactions rolled back */
totalRolledBack: number;
/** Total transactions failed */
totalFailed: number;
/** Total operations executed */
totalOperations: number;
/** Total conflicts encountered */
totalConflicts: number;
/** Total retries */
totalRetries: number;
/** Average transaction duration */
avgDuration: number;
/** Average operations per transaction */
avgOperationsPerTransaction: number;
/** Success rate */
successRate: number;
/** Active transactions count */
activeTransactions: number;
}
/**
* Lock information
*/
export interface LockInfo {
/** Document key (index:id) */
key: string;
/** Transaction ID holding the lock */
transactionId: string;
/** Lock type */
type: 'read' | 'write';
/** Lock acquired at */
acquiredAt: Date;
/** Lock expires at */
expiresAt: Date;
/** Lock metadata */
metadata?: Record<string, unknown>;
}
/**
* Conflict resolution strategy
*/
export type ConflictResolutionStrategy =
| 'abort' // Abort transaction
| 'retry' // Retry operation
| 'skip' // Skip conflicting operation
| 'force' // Force operation (last write wins)
| 'merge'; // Attempt to merge changes
/**
* Conflict information
*/
export interface ConflictInfo {
/** Operation that conflicted */
operation: TransactionOperation;
/** Conflicting transaction ID */
conflictingTransactionId?: string;
/** Expected version */
expectedVersion?: {
seqNo: number;
primaryTerm: number;
};
/** Actual version */
actualVersion?: {
seqNo: number;
primaryTerm: number;
};
/** Conflict detected at */
detectedAt: Date;
}
/**
* Transaction manager configuration
*/
export interface TransactionManagerConfig {
/** Default isolation level */
defaultIsolationLevel?: TransactionIsolationLevel;
/** Default locking strategy */
defaultLockingStrategy?: LockingStrategy;
/** Default transaction timeout */
defaultTimeout?: number;
/** Enable automatic cleanup of expired transactions */
enableCleanup?: boolean;
/** Cleanup interval in milliseconds */
cleanupInterval?: number;
/** Maximum concurrent transactions */
maxConcurrentTransactions?: number;
/** Conflict resolution strategy */
conflictResolution?: ConflictResolutionStrategy;
/** Enable transaction logging */
enableLogging?: boolean;
/** Enable transaction metrics */
enableMetrics?: boolean;
}
/**
* Savepoint for nested transactions
*/
export interface Savepoint {
/** Savepoint name */
name: string;
/** Operations count at savepoint */
operationsCount: number;
/** Created at */
createdAt: Date;
/** Metadata */
metadata?: Record<string, unknown>;
}
/**
* Transaction callback functions
*/
export interface TransactionCallbacks {
/** Called before transaction begins */
onBegin?: (context: TransactionContext) => Promise<void> | void;
/** Called before operation executes */
onBeforeOperation?: (operation: TransactionOperation) => Promise<void> | void;
/** Called after operation executes */
onAfterOperation?: (operation: TransactionOperation) => Promise<void> | void;
/** Called on conflict */
onConflict?: (conflict: ConflictInfo) => Promise<ConflictResolutionStrategy> | ConflictResolutionStrategy;
/** Called before commit */
onBeforeCommit?: (context: TransactionContext) => Promise<void> | void;
/** Called after commit */
onAfterCommit?: (result: TransactionResult) => Promise<void> | void;
/** Called before rollback */
onBeforeRollback?: (context: TransactionContext) => Promise<void> | void;
/** Called after rollback */
onAfterRollback?: (result: TransactionResult) => Promise<void> | void;
/** Called on transaction error */
onError?: (error: Error, context: TransactionContext) => Promise<void> | void;
}