@dataql/node
Version:
DataQL core SDK for unified data management with MongoDB and GraphQL - Production Multi-Cloud Ready
148 lines (147 loc) • 4.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateChecksum = generateChecksum;
exports.verifyChecksum = verifyChecksum;
exports.deepClone = deepClone;
exports.validateWALEntry = validateWALEntry;
exports.validateWALTransaction = validateWALTransaction;
exports.generateWALId = generateWALId;
exports.sanitizeOperationData = sanitizeOperationData;
exports.calculateEntrySize = calculateEntrySize;
exports.isEntryExpired = isEntryExpired;
exports.groupEntriesBySession = groupEntriesBySession;
exports.sortEntriesByTimestamp = sortEntriesByTimestamp;
const crypto_1 = require("crypto");
/**
* Generate checksum for WAL entry data integrity
*/
function generateChecksum(data) {
const serialized = JSON.stringify(data, Object.keys(data).sort());
return (0, crypto_1.createHash)("sha256").update(serialized).digest("hex");
}
/**
* Verify WAL entry checksum
*/
function verifyChecksum(entry) {
if (!entry.checksum)
return true; // No checksum to verify
const expectedChecksum = generateChecksum({
sessionId: entry.sessionId,
collection: entry.collection,
operation: entry.operation,
data: entry.data,
previousData: entry.previousData,
transactionId: entry.transactionId,
});
return entry.checksum === expectedChecksum;
}
/**
* Create a deep copy of data to prevent mutations
*/
function deepClone(obj) {
if (obj === null || typeof obj !== "object")
return obj;
if (obj instanceof Date)
return new Date(obj.getTime());
if (Array.isArray(obj))
return obj.map(deepClone);
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
/**
* Validate WAL entry structure
*/
function validateWALEntry(entry) {
const errors = [];
if (!entry.sessionId)
errors.push("sessionId is required");
if (!entry.collection)
errors.push("collection is required");
if (!entry.operation)
errors.push("operation is required");
if (!["create", "update", "delete", "transaction"].includes(entry.operation)) {
errors.push("operation must be create, update, delete, or transaction");
}
if (entry.data === undefined || entry.data === null) {
errors.push("data is required");
}
return errors;
}
/**
* Validate WAL transaction structure
*/
function validateWALTransaction(transaction) {
const errors = [];
if (!transaction.sessionId)
errors.push("sessionId is required");
if (!transaction.operations || !Array.isArray(transaction.operations)) {
errors.push("operations must be an array");
}
else if (transaction.operations.length === 0) {
errors.push("operations array cannot be empty");
}
return errors;
}
/**
* Generate unique WAL entry ID
*/
function generateWALId(prefix = "wal") {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 15);
return `${prefix}_${timestamp}_${random}`;
}
/**
* Convert operation data to WAL-safe format
*/
function sanitizeOperationData(operation, data, previousData) {
const sanitized = {
operation,
data: deepClone(data),
};
if (previousData !== undefined) {
sanitized.previousData = deepClone(previousData);
}
// Remove circular references and functions
return JSON.parse(JSON.stringify(sanitized));
}
/**
* Calculate WAL entry size in bytes
*/
function calculateEntrySize(entry) {
return Buffer.byteLength(JSON.stringify(entry), "utf8");
}
/**
* Check if WAL entry is expired based on retention policy
*/
function isEntryExpired(entry, maxRetentionDays) {
const maxAge = maxRetentionDays * 24 * 60 * 60 * 1000; // days to milliseconds
const entryAge = Date.now() - entry.timestamp.getTime();
return entryAge > maxAge;
}
/**
* Group WAL entries by session for batch processing
*/
function groupEntriesBySession(entries) {
const grouped = new Map();
for (const entry of entries) {
if (!grouped.has(entry.sessionId)) {
grouped.set(entry.sessionId, []);
}
grouped.get(entry.sessionId).push(entry);
}
return grouped;
}
/**
* Sort WAL entries by timestamp
*/
function sortEntriesByTimestamp(entries, ascending = true) {
return entries.sort((a, b) => {
const diff = a.timestamp.getTime() - b.timestamp.getTime();
return ascending ? diff : -diff;
});
}