UNPKG

mongodb-track-changes-rollback-plugin

Version:

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

132 lines (112 loc) 3.51 kB
import mongoose, { Schema, Document } from 'mongoose'; import { ChangeLog } from './models/ChangeLog'; import { ChangeSnapshot } from './models/ChangeSnapshot'; interface ChangeLoggerOptions { excludeFields?: string[]; operations?: { create?: boolean; update?: boolean; delete?: boolean; }; enableSnapshots?: boolean; snapshotInterval?: number; } interface DiffChange { [key: string]: [any, any]; } export function changeLoggerPlugin( schema: Schema, options: ChangeLoggerOptions = {} ) { const { excludeFields = [], operations = { create: true, update: true, delete: true }, enableSnapshots = false, snapshotInterval = 5, } = options; // Store original document state before save schema.pre('save', async function (next: (err?: any) => void) { const ModelClass = this.constructor as mongoose.Model<any>; try { if (this.isNew) { (this as any)._original = {}; } else { const original = await ModelClass.findById(this._id).lean(); (this as any)._original = original || {}; } next(); } catch (err) { next(err); } }); // Log create/update after save schema.post('save', async function (doc: Document) { const ModelClass = doc.constructor as mongoose.Model<any>; const isNew = doc.isNew; const excluded = new Set(excludeFields); const logOps = operations; if ((isNew && !logOps.create) || (!isNew && !logOps.update)) return; const original = (doc as any)._original || {}; const current = doc.toObject({ depopulate: true }); const changes: DiffChange = {}; for (const key of Object.keys(current)) { if (excluded.has(key) || key === '__v' || key === '_id') continue; const oldVal = original[key]; const newVal = current[key]; if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { changes[key] = [oldVal, newVal]; } } if (Object.keys(changes).length === 0 && !isNew) return; try { const collectionName = ModelClass.modelName; await ChangeLog.create({ documentId: doc._id, collectionName, operation: isNew ? 'create' : 'update', changes, version: doc.__v, createdAt: new Date(), }); if ( enableSnapshots && doc.__v !== undefined && doc.__v % snapshotInterval === 0 ) { await ChangeSnapshot.create({ documentId: doc._id, collectionName, version: doc.__v, snapshot: current, createdAt: new Date(), }); } } catch (err) { console.error('ChangeLog plugin error:', err); } }); // Log delete operations if (operations.delete) { schema.pre( 'deleteOne', { document: true, query: false }, async function (this: Document, next: (err?: any) => void) { try { const ModelClass = this.constructor as mongoose.Model<any>; const collectionName = ModelClass.modelName; await ChangeLog.create({ documentId: this._id, collectionName, operation: 'delete', changes: {}, // no changes for delete version: this.__v, createdAt: new Date(), }); next(); } catch (err) { next(err); } } ); } }