UNPKG

prisma-zod-generator

Version:

Prisma 2+ generator to emit Zod schemas from your Prisma schema

378 lines 15.7 kB
"use strict"; /** * Transaction Safety & Data Integrity Utilities * * Comprehensive ACID transaction support, rollback mechanisms, * and data integrity validation for production financial systems */ Object.defineProperty(exports, "__esModule", { value: true }); exports.transactionManager = exports.TransactionManager = exports.DataIntegrityError = exports.TransactionError = void 0; exports.transactional = transactional; const concurrency_1 = require("./concurrency"); class TransactionError extends Error { constructor(message, transactionId, operation, originalError) { super(message); this.transactionId = transactionId; this.operation = operation; this.originalError = originalError; this.name = 'TransactionError'; } } exports.TransactionError = TransactionError; class DataIntegrityError extends Error { constructor(message, violationType, context) { super(message); this.violationType = violationType; this.context = context; this.name = 'DataIntegrityError'; } } exports.DataIntegrityError = DataIntegrityError; /** * Transaction Manager for ACID compliance */ class TransactionManager { constructor() { this.transactions = new Map(); this.globalMutex = new concurrency_1.Mutex(); this.auditLog = []; } /** * Start a new ACID transaction */ async startTransaction(isolationLevel = 'read_committed') { const transactionId = this.generateTransactionId(); const serializableLock = isolationLevel === 'serializable' ? await this.globalMutex.acquire() : undefined; const context = { id: transactionId, startTime: Date.now(), operations: [], rollbackHandlers: [], status: 'pending', isolation_level: isolationLevel, serializableLock, }; this.transactions.set(transactionId, context); this.auditLog.push({ transactionId, operation: 'start_transaction', status: 'success', timestamp: new Date(), }); return transactionId; } /** * Execute operation within transaction context */ async executeInTransaction(transactionId, operation, operationInfo) { const transaction = this.transactions.get(transactionId); if (!transaction) { throw new TransactionError('Transaction not found', transactionId); } if (transaction.status !== 'pending') { throw new TransactionError(`Cannot execute operation in ${transaction.status} transaction`, transactionId); } const operationId = this.generateOperationId(); const transactionOp = { id: operationId, type: operationInfo.type, resource: operationInfo.resource, data: operationInfo.description, timestamp: Date.now(), completed: false, }; transaction.operations.push(transactionOp); if (operationInfo.rollback) { transaction.rollbackHandlers.unshift(operationInfo.rollback); } try { // Execute with appropriate isolation level const result = await this.executeWithIsolation(operation, transaction); transactionOp.completed = true; this.auditLog.push({ transactionId, operation: `${operationInfo.type}:${operationInfo.resource}`, status: 'success', timestamp: new Date(), }); return result; } catch (error) { transactionOp.completed = false; this.auditLog.push({ transactionId, operation: `${operationInfo.type}:${operationInfo.resource}`, status: 'failed', timestamp: new Date(), error: error instanceof Error ? error.message : 'Unknown error', }); throw new TransactionError(`Operation failed: ${operationInfo.description}`, transactionId, operationId, error instanceof Error ? error : new Error(String(error))); } } /** * Commit transaction with full ACID compliance */ async commitTransaction(transactionId) { const transaction = this.transactions.get(transactionId); if (!transaction) { throw new TransactionError('Transaction not found', transactionId); } try { if (transaction.status !== 'pending') { throw new TransactionError(`Cannot commit ${transaction.status} transaction`, transactionId); } // Validate all operations completed successfully const incompleteOps = transaction.operations.filter((op) => !op.completed); if (incompleteOps.length > 0) { throw new TransactionError(`Cannot commit transaction with incomplete operations: ${incompleteOps.map((op) => op.id).join(', ')}`, transactionId); } // Perform final consistency check await this.validateTransactionConsistency(transaction); // Mark transaction as committed transaction.status = 'committed'; this.auditLog.push({ transactionId, operation: 'commit_transaction', status: 'success', timestamp: new Date(), }); // Clean up transaction context this.transactions.delete(transactionId); } catch (error) { // Auto-rollback on commit failure await this.rollbackTransaction(transactionId); throw new TransactionError(`Transaction commit failed: ${error instanceof Error ? error.message : 'Unknown error'}`, transactionId, undefined, error instanceof Error ? error : new Error(String(error))); } finally { if (transaction.serializableLock) { transaction.serializableLock.release(); transaction.serializableLock = undefined; } } } /** * Rollback transaction with compensation */ async rollbackTransaction(transactionId) { const transaction = this.transactions.get(transactionId); if (!transaction) { throw new TransactionError('Transaction not found', transactionId); } if (transaction.status === 'rolled_back') { return; // Already rolled back } try { // Execute rollback handlers in reverse order for (const rollbackHandler of transaction.rollbackHandlers) { try { await rollbackHandler(); } catch (rollbackError) { console.error(`Rollback handler failed for transaction ${transactionId}:`, rollbackError); // Continue with other rollback handlers } } transaction.status = 'rolled_back'; this.auditLog.push({ transactionId, operation: 'rollback_transaction', status: 'success', timestamp: new Date(), }); } catch (error) { transaction.status = 'failed'; this.auditLog.push({ transactionId, operation: 'rollback_transaction', status: 'failed', timestamp: new Date(), error: error instanceof Error ? error.message : 'Unknown error', }); throw new TransactionError(`Transaction rollback failed: ${error instanceof Error ? error.message : 'Unknown error'}`, transactionId, undefined, error instanceof Error ? error : new Error(String(error))); } finally { if (transaction.serializableLock) { transaction.serializableLock.release(); transaction.serializableLock = undefined; } this.transactions.delete(transactionId); } } /** * Validate data integrity across related entities */ async validateDataIntegrity(data) { const violations = []; // Referential integrity checks for (const [subId, subscription] of data.subscriptions) { // Check customer exists if (!data.customers.has(subscription.customer_id)) { violations.push({ type: 'referential_integrity', message: `Subscription ${subId} references non-existent customer ${subscription.customer_id}`, severity: 'error', }); } } for (const [licenseKey, license] of data.licenses) { // Check subscription exists if (!data.subscriptions.has(license.subscription_id)) { violations.push({ type: 'referential_integrity', message: `License ${licenseKey} references non-existent subscription ${license.subscription_id}`, severity: 'error', }); } // Check license-subscription consistency const subscription = data.subscriptions.get(license.subscription_id); if (subscription) { if (license.plan !== subscription.plan) { violations.push({ type: 'data_consistency', message: `License ${licenseKey} plan (${license.plan}) doesn't match subscription plan (${subscription.plan})`, severity: 'error', }); } if (license.seats !== subscription.seats) { violations.push({ type: 'data_consistency', message: `License ${licenseKey} seats (${license.seats}) don't match subscription seats (${subscription.seats})`, severity: 'warning', }); } } } // Check for orphaned records for (const [customerId, customer] of data.customers) { if (customer.subscription_id && !data.subscriptions.has(customer.subscription_id)) { violations.push({ type: 'orphaned_reference', message: `Customer ${customerId} references non-existent subscription ${customer.subscription_id}`, severity: 'error', }); } } return { valid: violations.filter((v) => v.severity === 'error').length === 0, violations, }; } /** * Saga pattern for distributed transactions */ async executeSaga(steps) { const results = []; const compensations = []; try { for (const step of steps) { const result = await step.execute(); results.push(result); compensations.unshift(step.compensate); // Add to front for reverse order } return results; } catch (error) { // Execute compensations in reverse order for (const compensation of compensations) { try { await compensation(); } catch (compensationError) { console.error('Compensation failed:', compensationError); // Continue with other compensations } } throw error; } } /** * Get transaction audit log */ getAuditLog(filter) { let filtered = this.auditLog; if (filter === null || filter === void 0 ? void 0 : filter.transactionId) { filtered = filtered.filter((entry) => entry.transactionId === filter.transactionId); } if (filter === null || filter === void 0 ? void 0 : filter.operation) { filtered = filtered.filter((entry) => entry.operation.includes(filter.operation)); } if (filter === null || filter === void 0 ? void 0 : filter.status) { filtered = filtered.filter((entry) => entry.status === filter.status); } if (filter === null || filter === void 0 ? void 0 : filter.startDate) { filtered = filtered.filter((entry) => entry.timestamp >= filter.startDate); } if (filter === null || filter === void 0 ? void 0 : filter.endDate) { filtered = filtered.filter((entry) => entry.timestamp <= filter.endDate); } return filtered.slice(-1000); // Return last 1000 entries } // Private helper methods async executeWithIsolation(operation, transaction) { switch (transaction.isolation_level) { case 'serializable': // Lock acquired during startTransaction, just execute. return await operation(); case 'repeatable_read': case 'read_committed': case 'read_uncommitted': default: return await operation(); } } async validateTransactionConsistency(transaction) { // Check for any pending operations const pendingOps = transaction.operations.filter((op) => !op.completed); if (pendingOps.length > 0) { throw new DataIntegrityError(`Transaction has pending operations: ${pendingOps.map((op) => op.id).join(', ')}`, 'consistency'); } // Validate operation dependencies const createOps = transaction.operations.filter((op) => op.type === 'create'); const updateOps = transaction.operations.filter((op) => op.type === 'update'); for (const updateOp of updateOps) { const relatedCreate = createOps.find((createOp) => createOp.resource === updateOp.resource || createOp.data === updateOp.data); if (relatedCreate && updateOp.timestamp < relatedCreate.timestamp) { throw new DataIntegrityError(`Update operation ${updateOp.id} executed before related create operation ${relatedCreate.id}`, 'consistency'); } } } generateTransactionId() { return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } generateOperationId() { return `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } exports.TransactionManager = TransactionManager; /** * Global transaction manager instance */ exports.transactionManager = new TransactionManager(); /** * Decorator for transactional methods */ function transactional(isolationLevel) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const transactionId = await exports.transactionManager.startTransaction(isolationLevel); try { const result = await exports.transactionManager.executeInTransaction(transactionId, () => originalMethod.apply(this, args), { type: 'update', resource: `${target.constructor.name}.${propertyKey}`, description: `Execute ${propertyKey} method`, }); await exports.transactionManager.commitTransaction(transactionId); return result; } catch (error) { await exports.transactionManager.rollbackTransaction(transactionId); throw error; } }; return descriptor; }; } //# sourceMappingURL=transactionSafety.js.map