Never Pummel Muskoxen

    @compwright/mongoose-patch-history

    1.3.1 • Public • Published

    npm version Build Status Greenkeeper badge Known Vulnerabilities Coverage Status

    Mongoose Patch History is a mongoose plugin that saves a history of JSON Patch operations for all documents belonging to a schema in an associated "patches" collection.

    Installation

    $ npm install @compwright/mongoose-patch-history
    

    Usage

    To use @compwright/mongoose-patch-history for an existing mongoose schema you can simply plug it in. As an example, the following schema definition defines a Post schema, and uses @compwright/mongoose-patch-history with default options:

    import mongoose, { Schema } from 'mongoose'
    import patchHistory from '@compwright/mongoose-patch-history'
    
    const PostSchema = new Schema({
      title: { type: String, required: true },
      comments: Array,
    })
    
    PostSchema.plugin(patchHistory, { mongoose, name: 'postPatches' })
    const Post = mongoose.model('Post', PostSchema)

    mongoose-patch-history will define a schema that has a ref field containing the ObjectId of the original document, a ops array containing all json patch operations and a date field storing the date where the patch was applied.

    Storing a new document

    Continuing the previous example, a new patch is added to the associated patch collection whenever a new post is added to the posts collection:

    Post.create({ title: 'JSON patches' })
      .then(post => post.patches.findOne({ ref: post.id }))
      .then(console.log)
    
    // {
    //   _id: ObjectId('4edd40c86762e0fb12000003'),
    //   ref: ObjectId('4edd40c86762e0fb12000004'),
    //   ops: [
    //     { value: 'JSON patches', path: '/title', op: 'add' },
    //     { value: [], path: '/comments', op: 'add' }
    //   ],
    //   date: new Date(1462360838107),
    //   __v: 0
    // }

    Updating an existing document

    mongoose-patch-history also adds a static field Patches to the model that can be used to access the patch model associated with the model, for example to query all patches of a document. Whenever a post is edited, a new patch that reflects the update operation is added to the associated patch collection:

    const data = {
      title: 'JSON patches with mongoose',
      comments: [{ message: 'Wow! Such Mongoose! Very NoSQL!' }],
    }
    
    Post.create({ title: 'JSON patches' })
      .then(post => post.set(data).save())
      .then(post => post.patches.find({ ref: post.id }))
      .then(console.log)
    
    // [{
    //   _id: ObjectId('4edd40c86762e0fb12000003'),
    //   ref: ObjectId('4edd40c86762e0fb12000004'),
    //   ops: [
    //     { value: 'JSON patches', path: '/title', op: 'add' },
    //     { value: [], path: '/comments', op: 'add' }
    //   ],
    //   date: new Date(1462360838107),
    //   __v: 0
    // }, {
    //   _id: ObjectId('4edd40c86762e0fb12000005'),
    //   ref: ObjectId('4edd40c86762e0fb12000004'),
    //   ops: [
    //     { value: { message: 'Wow! Such Mongoose! Very NoSQL!' }, path: '/comments/0', op: 'add' },
    //     { value: 'JSON patches with mongoose', path: '/title', op: 'replace' }
    //   ],
    //   "date": new Date(1462361848742),
    //   "__v": 0
    // }]

    Rollback to a specific patch

    Documents have a rollback method that accepts the ObjectId of a patch doc and sets the document to the state of that patch, adding a new patch to the history.

    Post.create({ title: 'First version' })
      .then(post => post.set({ title: 'Second version' }).save())
      .then(post => post.set({ title: 'Third version' }).save())
      .then(post => {
        return post.patches
          .find({ ref: post.id })
          .then(patches => post.rollback(patches[1].id))
      })
      .then(console.log)
    
    // {
    //   _id: ObjectId('4edd40c86762e0fb12000006'),
    //   title: 'Second version',
    //   __v: 0
    // }

    The rollback method will throw an Error when invoked with an ObjectId that is

    • not a patch of the document
    • the latest patch of the document

    Options

    PostSchema.plugin(patchHistory, {
      mongoose,
      name: 'postPatches',
    })
    • mongoose 📌 required
      The mongoose instance to work with
    • name 📌 required
      String where the names of both patch model and patch collection are generated from. By default, model name is the pascalized version and collection name is an undercore separated version
    • removePatches
      Removes patches when origin document is removed. Default: true
    • transforms
      An array of two functions that generate model and collection name based on the name option. Default: An array of humps.pascalize and humps.decamelize
    • includes
      Property definitions that will be included in the patch schema. Read more about includes in the next chapter of the documentation. Default: {}
    • trackOriginalValue
      If enabled, the original value will be stored in the change patches under the attribute originalValue. Default: false

    Includes

    PostSchema.plugin(patchHistory, {
      mongoose,
      name: 'postPatches',
      includes: {
        title: { type: String, required: true },
      },
    })

    This will add a title property to the patch schema. All options that are available in mongoose's schema property definitions such as required, default or index can be used.

    Post.create({ title: 'Included in every patch' })
      .then((post) => post.patches.findOne({ ref: post.id })
      .then((patch) => {
        console.log(patch.title) // 'Included in every patch'
      })

    The value of the patch documents properties is read from the versioned documents property of the same name.

    Reading from virtuals

    There is an additional option that allows storing information in the patch documents that is not stored in the versioned documents. To do so, you can use a combination of virtual type setters on the versioned document and an additional from property in the include options of mongoose-patch-history:

    // save user as _user in versioned documents
    PostSchema.virtual('user').set(function(user) {
      this._user = user
    })
    
    // read user from _user in patch documents
    PostSchema.plugin(patchHistory, {
      mongoose,
      name: 'postPatches',
      includes: {
        user: { type: Schema.Types.ObjectId, required: true, from: '_user' },
      },
    })
    
    // create post, pass in user information
    Post.create({
      title: 'Why is hiring broken?',
      user: mongoose.Types.ObjectId(),
    })
      .then(post => {
        console.log(post.user) // undefined
        return post.patches.findOne({ ref: post.id })
      })
      .then(patch => {
        console.log(patch.user) // 4edd40c86762e0fb12000012
      })

    In case of a rollback in this scenario, the rollback method accepts an object as its second parameter where additional data can be injected:

    Post.create({ title: 'v1', user: mongoose.Types.ObjectId() })
      .then(post =>
        post
          .set({
            title: 'v2',
            user: mongoose.Types.ObjectId(),
          })
          .save()
      )
      .then(post => {
        return post.patches.find({ ref: post.id }).then(patches =>
          post.rollback(patches[0].id, {
            user: mongoose.Types.ObjectId(),
          })
        )
      })

    Reading from query options

    In situations where you are running Mongoose queries directly instead of via a document, you can specify the extra fields in the query options:

    Post.findOneAndUpdate(
      { _id: '4edd40c86762e0fb12000012' },
      { title: 'Why is hiring broken? (updated)' },
      { _user: mongoose.Types.ObjectId() }
    )

    Install

    npm i @compwright/mongoose-patch-history

    DownloadsWeekly Downloads

    1

    Version

    1.3.1

    License

    MIT

    Unpacked Size

    51.3 kB

    Total Files

    10

    Last publish

    Collaborators

    • compwright