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
text/typescript
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);
}
}