UNPKG

@dataql/node

Version:

DataQL core SDK for unified data management with MongoDB and GraphQL - Production Multi-Cloud Ready

252 lines (251 loc) 9.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseWAL = void 0; const types_js_1 = require("./types.js"); const utils_js_1 = require("./utils.js"); class BaseWAL { constructor(config = {}) { this.config = { maxRetentionDays: 7, autoCommit: false, checksumEnabled: true, batchSize: 100, enableRecovery: true, ...config, }; } async writeEntry(entryData) { // Validate entry data const validationErrors = (0, utils_js_1.validateWALEntry)(entryData); if (validationErrors.length > 0) { throw new types_js_1.WALError(`Invalid WAL entry: ${validationErrors.join(", ")}`, "INVALID_ENTRY", entryData); } // Create complete entry const entry = { id: (0, utils_js_1.generateWALId)("wal"), timestamp: new Date(), status: "pending", ...entryData, }; // Generate checksum if enabled if (this.config.checksumEnabled) { entry.checksum = (0, utils_js_1.generateChecksum)({ sessionId: entry.sessionId, collection: entry.collection, operation: entry.operation, data: entry.data, previousData: entry.previousData, transactionId: entry.transactionId, }); } // Sanitize operation data const sanitized = (0, utils_js_1.sanitizeOperationData)(entry.operation, entry.data, entry.previousData); entry.data = sanitized.data; entry.previousData = sanitized.previousData; try { await this.persistEntry(entry); // Auto-commit if enabled if (this.config.autoCommit) { await this.commitEntry(entry.id); } return entry; } catch (error) { throw new types_js_1.WALError(`Failed to write WAL entry: ${error instanceof Error ? error.message : "Unknown error"}`, "WRITE_FAILED", entry); } } async writeTransaction(transactionData) { // Validate transaction data const validationErrors = (0, utils_js_1.validateWALTransaction)(transactionData); if (validationErrors.length > 0) { throw new types_js_1.WALError(`Invalid WAL transaction: ${validationErrors.join(", ")}`, "INVALID_TRANSACTION"); } // Create complete transaction const transaction = { id: (0, utils_js_1.generateWALId)("txn"), timestamp: new Date(), status: "pending", ...transactionData, }; // Validate all operations in the transaction for (const operation of transaction.operations) { const operationErrors = (0, utils_js_1.validateWALEntry)(operation); if (operationErrors.length > 0) { throw new types_js_1.WALError(`Invalid operation in transaction: ${operationErrors.join(", ")}`, "INVALID_TRANSACTION_OPERATION", operation); } // Set transaction ID on all operations operation.transactionId = transaction.id; } try { await this.persistTransaction(transaction); // Auto-commit if enabled if (this.config.autoCommit) { await this.commitTransaction(transaction.id); } return transaction; } catch (error) { throw new types_js_1.WALError(`Failed to write WAL transaction: ${error instanceof Error ? error.message : "Unknown error"}`, "TRANSACTION_WRITE_FAILED"); } } async commitEntry(entryId) { try { await this.updateEntryStatus(entryId, "committed"); } catch (error) { throw new types_js_1.WALError(`Failed to commit WAL entry ${entryId}: ${error instanceof Error ? error.message : "Unknown error"}`, "COMMIT_FAILED"); } } async commitTransaction(transactionId) { try { await this.updateTransactionStatus(transactionId, "committed"); } catch (error) { throw new types_js_1.WALError(`Failed to commit WAL transaction ${transactionId}: ${error instanceof Error ? error.message : "Unknown error"}`, "TRANSACTION_COMMIT_FAILED"); } } async rollbackEntry(entryId) { try { await this.updateEntryStatus(entryId, "rollback"); } catch (error) { throw new types_js_1.WALError(`Failed to rollback WAL entry ${entryId}: ${error instanceof Error ? error.message : "Unknown error"}`, "ROLLBACK_FAILED"); } } async rollbackTransaction(transactionId) { try { await this.updateTransactionStatus(transactionId, "rollback"); } catch (error) { throw new types_js_1.WALError(`Failed to rollback WAL transaction ${transactionId}: ${error instanceof Error ? error.message : "Unknown error"}`, "TRANSACTION_ROLLBACK_FAILED"); } } async getUncommittedEntries(sessionId) { try { const entries = await this.fetchUncommittedEntries(sessionId); // Verify checksums if enabled if (this.config.checksumEnabled) { const validEntries = entries.filter((entry) => { if (!(0, utils_js_1.verifyChecksum)(entry)) { console.warn(`WAL entry ${entry.id} failed checksum verification`); return false; } return true; }); return validEntries; } return entries; } catch (error) { throw new types_js_1.WALError(`Failed to fetch uncommitted entries: ${error instanceof Error ? error.message : "Unknown error"}`, "FETCH_FAILED"); } } async getEntriesSince(timestamp, sessionId) { try { const entries = await this.fetchEntriesSince(timestamp, sessionId); // Verify checksums if enabled if (this.config.checksumEnabled) { const validEntries = entries.filter((entry) => { if (!(0, utils_js_1.verifyChecksum)(entry)) { console.warn(`WAL entry ${entry.id} failed checksum verification`); return false; } return true; }); return validEntries; } return entries; } catch (error) { throw new types_js_1.WALError(`Failed to fetch entries since ${timestamp}: ${error instanceof Error ? error.message : "Unknown error"}`, "FETCH_FAILED"); } } async createRecoveryPoint(sessionId, walEntryId) { if (!this.config.enableRecovery) { throw new types_js_1.WALError("Recovery is disabled in WAL configuration", "RECOVERY_DISABLED"); } const recoveryPoint = { id: (0, utils_js_1.generateWALId)("recovery"), timestamp: new Date(), walEntryId, sessionId, }; try { await this.persistRecoveryPoint(recoveryPoint); return recoveryPoint; } catch (error) { throw new types_js_1.WALError(`Failed to create recovery point: ${error instanceof Error ? error.message : "Unknown error"}`, "RECOVERY_POINT_FAILED"); } } async purgeCommittedEntries(olderThan) { try { return await this.deleteCommittedEntries(olderThan); } catch (error) { throw new types_js_1.WALError(`Failed to purge committed entries: ${error instanceof Error ? error.message : "Unknown error"}`, "PURGE_FAILED"); } } async purgeSession(sessionId) { try { await this.deleteSessionEntries(sessionId); } catch (error) { throw new types_js_1.WALError(`Failed to purge session ${sessionId}: ${error instanceof Error ? error.message : "Unknown error"}`, "SESSION_PURGE_FAILED"); } } /** * Execute a mutation with WAL logging */ async executeWithWAL(sessionId, collection, operation, data, mutationFn, previousData, metadata) { // Write WAL entry first const walEntry = await this.writeEntry({ sessionId, collection, operation, data: (0, utils_js_1.deepClone)(data), previousData: previousData ? (0, utils_js_1.deepClone)(previousData) : undefined, metadata, }); try { // Execute the actual mutation const result = await mutationFn(); // Commit the WAL entry on success if (!this.config.autoCommit) { await this.commitEntry(walEntry.id); } return { walEntry, result, rollback: async () => { await this.rollbackEntry(walEntry.id); }, }; } catch (error) { // Rollback WAL entry on failure await this.rollbackEntry(walEntry.id); throw error; } } /** * Cleanup expired entries based on retention policy */ async cleanupExpiredEntries() { const cutoffDate = new Date(Date.now() - this.config.maxRetentionDays * 24 * 60 * 60 * 1000); return await this.purgeCommittedEntries(cutoffDate); } /** * Get WAL configuration */ getConfig() { return { ...this.config }; } /** * Update WAL configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } } exports.BaseWAL = BaseWAL;