UNPKG

mongodb-track-changes-rollback-plugin

Version:

A plugin for tracking create, update, and delete operations with rollback support and snapshots for MongoDB/Mongoose

101 lines (86 loc) 3.19 kB
import { ChangeLog } from './models/ChangeLog'; import { ChangeSnapshot } from './models/ChangeSnapshot'; import mongoose from 'mongoose'; interface RollbackParams { model: mongoose.Model<any>; documentId: string; toVersion: number; useSnapshots?: boolean; } interface DocWithVersion { [key: string]: any; __v?: number; } export async function rollbackToVersion({ model, documentId, toVersion, useSnapshots = true, }: RollbackParams) { const collectionName = model.modelName; // Load current document state const currentDoc = await model.findById(documentId).lean() as DocWithVersion | null; if (!currentDoc) throw new Error('Document not found'); const currentVersion = currentDoc.__v ?? 0; if (toVersion >= currentVersion) { throw new Error(`Target version ${toVersion} must be less than current version ${currentVersion}`); } // Rollback using snapshots + diffs if (useSnapshots) { // Find the latest snapshot at or before the target version const snapshot = await ChangeSnapshot.findOne({ documentId, collectionName, version: { $lte: toVersion }, }).sort({ version: -1 }); if (!snapshot) { throw new Error('No snapshot found for rollback. Please create snapshots or disable useSnapshots.'); } // Start with snapshot state let state: DocWithVersion = { ...snapshot.snapshot }; // Find all diffs from snapshot version up to target version const diffs = await ChangeLog.find({ documentId, collectionName, operation: 'update', version: { $gt: snapshot.version, $lte: toVersion }, }).sort({ version: 1 }); // Apply diffs forward to reach target version for (const log of diffs) { for (const [field, [, newVal]] of Object.entries(log.changes)) { state[field] = newVal; } state.__v = log.version; } // Update the document in DB await model.findByIdAndUpdate(documentId, state); } else { // Rollback using diffs only (from current version down to target version) let state: DocWithVersion = { ...currentDoc }; // Find diffs after the target version to current version (descending order) const logs = await ChangeLog.find({ documentId, collectionName, operation: 'update', version: { $gt: toVersion, $lte: currentVersion }, }).sort({ version: -1 }); // Revert each diff (go backwards) for (const log of logs) { for (const [field, change] of Object.entries(log.changes)) { if (Array.isArray(change) && change.length === 2) { const [oldVal, newVal] = change; // Only revert if current state matches newVal if (JSON.stringify(state[field]) === JSON.stringify(newVal)) { state[field] = oldVal; } } else { throw new Error(`Invalid changes format for rollback on field ${field}`); } } // Decrement version after rollback state.__v = log.version - 1; } // Save reverted state await model.findByIdAndUpdate(documentId, state); } }