@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
JavaScript
;
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;