Mongoose Track allows you to track and manage document changes (deeply) with author references.
npm i mongoose-track --save
const mongoose = require('mongoose')
const mongooseTrack = require('mongoose-track')
mongooseTrack.options = { ... }
let fruitSchema = new mongoose.Schema({
name: { type: String },
color: { type: String }
})
fruitSchema.plugin(mongooseTrack.plugin, { ... })
let fruitModel = mongoose.model('fruitModel', fruitSchema)
All changes to fruitModel
documents will now be written to the document under document.history
.
You can set options globaly or per schema by passing a second argument to the plugin. Schema-specific options override global options.
To set global options:
const mongooseTrack = require('mongoose-track')
mongooseTrack.options = { /*options*/ }
To set schema-specific options:
const mongoose = require('mongoose')
const mongooseTrack = require('mongoose-track')
let mySchema = new mongoose.Schema({ ... })
mySchema.plugin(mongooseTrack.plugin, { /*options*/ }
-
historyIgnore
indicates whether field history should be trackedtrue
Changes to this property will not be added to thehistoryEvent
false
Changes to this property will be added to thehistoryEvent
let fruitSchema = new mongoose.Schema({ name: { type: String }, color: { type: String, historyIgnore: true } })
-
options.track.N
, type:Boolean
, default:true
. Indicates whether history for newly created fields should be tracked. -
options.track.E
, type:Boolean
, default:true
. Indicates whether history for edited (i.e. previously defined and changed) created fields should be tracked. -
options.track.D
, type:Boolean
, default:true
. Indicates whether history for deleted (i.e. previously defined and removed) fields should be tracked. -
options.track.A
, type:Boolean
, default:true
. Indicates whether history for array fields should be tracked. -
options.author.enabled
, type:Boolean
, default:false
. Indicated whetherdocument.historyAuthor
will be added to history. -
options.author.type
, type:Mixed
, default:mongoose.Schema.Types.String
. This should be set to the_id
type of the author document, typically you'll usemongoose.Schema.Types.ObjectId
. -
options.author.ref
, type:String
, default:undefined
. This should be set to the model name of the author document, such as"userModel"
A historyEvent
is created when you save a document, if there are (tracked) property changes to that document, they will be appended to the historyEvent
and the historyEvent
will be placed at the top of the document.history
array. Otherwise, no historyEvent
will be saved.
history: [{
_id: ObjectId,
date: Date,
author: Mixed,
changes: [{ ... }]
}]
[historyEvent]
, type:Array
. This array contains allhistoryEvent
's for the documenthistoryEvent.date
, type:Date
, default:new Date()
. This value is set just beforestatic.save()
is firedhistoryEvent.author
, type:Mixed
. This value is set fromdocument.historyAuthor
, assumingoptions.author.enabled === true
A historyChangeEvent
is a (singular) change to a document property that occurred within document.history[].changes
.
[{
_id: ObjectId,
path: [String],
before: Mixed,
after: Mixed
}]
[historyChangeEvent]
, type:Array
- This array contains all
historyChangeEvent
's made within the currenthistoryEvent
historyChangeEvent.path
, type:[String]
- This array denotes a reference to the changed key, for example:
{ color: { primary: "blue" } } === [ 'color', 'primary' ]
historyChangeEvent.before
, type:Mixed
- This value is taken from the property (located at
historyChangeEvent.path
) before being saved
historyChangeEvent.after
, type:Mixed
- This value is taken from the property (located at
historyChangeEvent.path
) after being saved
-
method.historyRevise(query, deepRevision)
, query:Mixed
, deepRevision:Boolean
.- If the
query
value is anObjectId
value from ahistoryEvent
orhistoryChangeEvent
, this will return a document with values matching thehistoryEvent._id || historyChangeEvent._id
- If the
query
value is aDate
value it will find the latesthistoryEvent
that occurred prior to theDate
value. - If
deepRevision
is set totrue
, a deep revision will occur. This will revise the document to exactly how it was when the matchinghistoryEvent
was created by setting all prior values from oldest to latest, stopping at the matching **storyEvent
. - If
deepRevision
is set tofalse
, only the changes within the matchinghistoryEvent
orhistoryChangeEvent
will be revised. - Currently
deepRevision
does not support aquery
value of ahistoryChangeEvent
ObjectId
.
- If the
-
method.historyForget(historyEventId, single)
, query:ObjectId
, deepRevision:Boolean
. This method accepts an_id
from ahistoryEvent
and will remove alldocument.history
prior to and including the matchinghistoryEvent
. Ifsingle
is set totrue
, only the matchinghistoryEvent
will be removed.
-
static.historyFind(query)
, query:mongoose.Query
. This static allows you to pass additional query operators tostatic.find()
. Passing$revision
to the query with aDate
value will return matching documents revised to that date, usesmethod._revise()
. Additionally you can define$deepRevision
to return documents with a deep revision, same asmethod._revise()
. -
static.historyFindOne(query)
, query:mongoose.Query
. This static allows you to pass additional query operators tostatic.findOne()
Passing$revision
to the query with aDate
value will return a matching document revised to that date, same asmethod._revise()
Additionally you can define$deepRevision
to return documents with a deep revision, same asmethod._revise()
What properties are excluded from a
historyEvent
by default?
Changes to the following: ['_id', '__v', 'history', 'historyAuthor']
will not be recorded, along with any schema properties that have historyIgnore === true
.
If a
historyEvent
occurs but nohistoryChangeEvent
's are logged, is it recorded?
No. If the history.changes
Array is empty, the historyEvent
will not be saved.
Can I pick where the history is stored? (other than
document.history
)
Not yet, in the future you'll be able to set most if not all of the Mongoose Track keys, methods and statics.
Clone this repository and run example.js
git clone https://github.com/brod/mongoose-track.git
cd mongoose-track
node example.js
You should see the output of all **storyEvent
's and historyChangeEvent
's to a document including manual changes, authored changes, forget changes and a revision.
This will connect to mongodb://localhost/mongooseTrackExample
The example below uses the minimum setup.
const mongoose = require('mongoose')
const mongooseTrack = require('mongoose-track')
let fruitSchema = new mongoose.Schema({
name: { type: String },
color: { type: String }
})
fruitSchema.plugin(mongooseTrack.plugin)
let fruitModel = mongoose.model('fruitModel', fruitSchema)
The example below does not track N
events (newly created fields).
const mongoose = require('mongoose')
const mongooseTrack = require('mongoose-track')
mongooseTrack.options = {
track: {
N: false
}
}
let fruitSchema = new mongoose.Schema({
name: { type: String },
color: { type: String }
})
fruitSchema.plugin(mongooseTrack.plugin)
let fruitModel = mongoose.model('fruitModel', fruitSchema)
The example below appends an author to events.
const mongoose = require('mongoose')
const mongooseTrack = require('mongoose-track')
mongooseTrack.options = {
author: {
enable: true,
ref: 'userModel'
}
let fruitSchema = new mongoose.Schema({
name: { type: String },
color: { type: String }
})
fruitSchema.plugin(mongooseTrack.plugin)
let fruitModel = mongoose.model('fruitModel', fruitSchema)
let userSchema = new mongoose.Schema({
name: { type: String },
color: { type: String }
})
userSchema.plugin(mongooseTrack.plugin)
let userModel = mongoose.model('userModel', userSchema)
To pass the author reference, set document.historyAuthor
before you save the document.
var fruit = new fruitModel({
name: 'Banana',
color: 'Yellow',
historyAuthor: '507f191e810c19729de860ea'
})
fruit.save()
/* Document
{
name: 'Banana',
color: 'Yellow',
history: [{
date: ...
author: '507f191e810c19729de860ea',
changes: [{
type: 'N',
path: [],
after: {
name: 'Banana'
color: 'Yellow'
}
}]
}
*/
Feel free to send pull requests and submit issues 😉