Automattic / Automattic/mongoose
What's the best way to keep track of the changes made to a document?
- 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
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