Automattic / Automattic/mongoose

What's the best way to keep track of the changes made to a document?

Open
#11,644 11 comments 3 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
27.5k
Forks
4k
Avg merge
2d 7h
Merged PRs (30d)
35

Description

Mongoose version: 6.2.2

Hi. What would be the best option to keep track of the changes that have been made to a document? I'm trying to keep an array of changes like this:
```javascript
[
{path: '/my/path/', old_value: 'previous_value_for this path'}
]
```
And I want to achieve this the most transparent way for our developers teams. This means that I want to keep changes no matter how a document is updated. So if `myDocument` is `{prop: 2}`, then we may update `myDocument` using any of this ways:
```javascript
myDocument.prop = 1;
myDocument.set('prop', 1);
```
Or applying a JSON Patch:
```javascript
const patch = [{op: 'replace', path: '/prop', value: 1}];
fastJsonPatch.applyPatch(myDocument, patch, false, true, true); //This updates the document instead of creating a new one
```
In any of the options the target is to have an array `_changes` that in any of the examples above would be like this after applying the changes:
```javascript
[{path: '/prop', old_value: 1}]
```
In order to achieve this feature I've created a Mongoose plugin:

```javascript
const util = require('util');
const jsonpatch = require('fast-json-patch')

const proxy_handler = {
apply: function (target, this_arg, arglist){
const path = '/' + arglist[0].split('.').filter(p => p).join('/');
const old_value = this_arg.get(arglist[0]);
const change = {path, old_value};
if(this_arg._changes){
if(!this_arg._changes.some(change => change.path === path && util.isDeepStrictEqual(old_value, change.old_value))){//This change does not exist yet. (the same change could already exist because markModified is recursive
this_arg._changes.unshift(change); //we insert the changes at the beggining of the array because if we have to revert the changes it is not neccesary to revert the array.
}
}else{
this_arg._changes = [change];
}
const newtarget = target.bind(this_arg);
newtarget(...arglist);
}
};

const changesTracker = schema => {
schema.post('init', function(doc){
const $setProxy = new Proxy(doc.$set, proxy_handler);
const setProxy = new Proxy(doc.set, proxy_handler);
const markModifiedProxy = new Proxy(doc.markModified, proxy_handler);
doc.$set = $setProxy;
doc.set = setProxy;
doc.markModified = markModifiedProxy;
});

schema.pre('save', function(next){
if(this.isNew){//we do the same that in the post-init middleware because when a model is created using mongoose the init middleware does not apply
const $setProxy = new Proxy(this.$set, proxy_handler);
const setProxy = new Proxy(this.set, proxy_handler);
const markModifiedProxy = new Proxy(this.markModified, proxy_handler);
this.$set = $setProxy;
this.set = setProxy;
this.markModified = markModifiedProxy;
this._changes = [{op: 'replace', path: '', old_value: undefined}]
}
next();
});

schema.pre('remove', function(next){
this._changes = [{op: 'replace', path: '', old_value: this}]
next();
});
}
module.exports = changesTracker;
```
And this is working fine for almost every change but when you are updating a single value in a deeply nested array.
Example:
We have this document:
```
{
a: 1,
b: {
b1: [1,2]
}
}
```
Then we apply this patch:
```javascript
const path = [{op: 'replace', path: '/b/b1/1', value: 3}];
fastjsonpatch.applyPatch(myDocument, patch, false, true, true);
```
And at this point I'm a little stuck and any insight would be highly appreciated.
Thank you.

Contributor guide

Open the contributing guide

Research direction

No repository file or test is named. Start by reading Mongoose document change tracking around $set, set, markModified, and the schema init/save/remove hooks, then compare those paths with fast-json-patch mutations of nested arrays. Done would require a clearly defined, maintainable way to record prior values for every stated update path.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, mongodb
Domain
backend, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.