All Projects → codepunkt → Mongoose Patch History

codepunkt / Mongoose Patch History

Licence: mit
Mongoose plugin that saves a history of JSON patch operations for all documents belonging to a schema in an associated 'patches' collection

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Mongoose Patch History

Graphql Advanced Projection
Fully customizable Mongoose/MongoDB projection generator.
Stars: ✭ 46 (-43.9%)
Mutual labels:  mongoose, mongodb
Wertik Js
💪 A library that powers your app with GraphQL + Rest API
Stars: ✭ 56 (-31.71%)
Mutual labels:  mongoose, mongodb
Mongomem
In-memory MongoDB Server. Ideal for testing.
Stars: ✭ 51 (-37.8%)
Mutual labels:  mongoose, mongodb
Meteor Collection2
A Meteor package that extends Mongo.Collection to provide support for specifying a schema and then validating against that schema when inserting and updating.
Stars: ✭ 1,020 (+1143.9%)
Mutual labels:  schema, mongodb
Fullstack Shopping Cart
MERN stack shopping cart, written in TypeScript
Stars: ✭ 82 (+0%)
Mutual labels:  mongoose, mongodb
Graphql Factory
A toolkit for building GraphQL
Stars: ✭ 44 (-46.34%)
Mutual labels:  json, schema
Node React Ecommerce
Build ECommerce Website Like Amazon By React & Node & MongoDB
Stars: ✭ 1,080 (+1217.07%)
Mutual labels:  mongoose, mongodb
Checksheet Manager
Checksheet Manager for college checksheets. Created with AngularJS and Node/Express/MongoDB.
Stars: ✭ 31 (-62.2%)
Mutual labels:  mongoose, mongodb
Mean Stack Angular6 Crud Example
MEAN Stack Angular 6 CRUD Web Application
Stars: ✭ 69 (-15.85%)
Mutual labels:  mongoose, mongodb
Govalid
Data validation library for golang. [MIGRATING TO NEW ADDRESS]
Stars: ✭ 59 (-28.05%)
Mutual labels:  json, schema
Typegoose
Typegoose - Define Mongoose models using TypeScript classes.
Stars: ✭ 1,232 (+1402.44%)
Mutual labels:  mongoose, mongodb
Typegoose
Typegoose - Define Mongoose models using TypeScript classes.
Stars: ✭ 1,189 (+1350%)
Mutual labels:  mongoose, mongodb
Mean Angular4 Chat App
MEAN stack with Angular 4 Chat App
Stars: ✭ 41 (-50%)
Mutual labels:  mongoose, mongodb
Ha Db
探索高效的SAAS结构,单体应用动态切换数据库。
Stars: ✭ 44 (-46.34%)
Mutual labels:  schema, mongodb
Cmms
Computerized Maintenance Management System
Stars: ✭ 31 (-62.2%)
Mutual labels:  mongoose, mongodb
Vue Element Responsive Demo
基于 Vue + Element 的响应式后台模板
Stars: ✭ 54 (-34.15%)
Mutual labels:  mongoose, mongodb
Treefrog Framework
TreeFrog Framework : High-speed C++ MVC Framework for Web Application
Stars: ✭ 885 (+979.27%)
Mutual labels:  json, mongodb
Jwt Node Vue
Repositório responsável pelo primeiro projeto da série de vídeos: Coding Stuff.
Stars: ✭ 29 (-64.63%)
Mutual labels:  mongoose, mongodb
Rest Hapi
🚀 A RESTful API generator for Node.js
Stars: ✭ 1,102 (+1243.9%)
Mutual labels:  mongoose, mongodb
Avocado
Strongly-typed MongoDB driver for Rust
Stars: ✭ 70 (-14.63%)
Mutual labels:  json, mongodb

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 mongoose-patch-history

Usage

To use 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 mongoose-patch-history with default options:

import mongoose, { Schema } from 'mongoose'
import patchHistory from 'mongoose-patch-history'

/* or the following if not running your app with babel:
const patchHistory = require('mongoose-patch-history').default;
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
*/

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

rollback(ObjectId, data, save)

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
// }

Injecting data

Further the rollback method accepts a data object which is injected into the document.

post.rollback(patches[1].id, { name: 'merged' })

// {
//   _id: ObjectId('4edd40c86762e0fb12000006'),
//   title: 'Second version',
//   name: 'merged',
//   __v: 0
// }

Rollback without saving

To rollback the document to a specific patch but without saving it back to the database call the method with an empty data object and the save flag set to false.

post.rollback(patches[1].id, {}, false)

// Returns the document without saving it back to the db.
// {
//   _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: {}
  • excludes
    Property paths that will be excluded in patches. Read more about excludes in the excludes 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() }
)

Excludes

PostSchema.plugin(patchHistory, {
  mongoose,
  name: 'postPatches',
  excludes: [
    '/path/to/hidden/property',
    '/path/into/array/*/property',
    '/path/to/one/array/1/element',
  ],
})

// Properties
// /path/to/hidden:                   included
// /path/to/hidden/property:          excluded
// /path/to/hidden/property/nesting:  excluded

// Array element properties
// /path/into/array/0:                included
// /path/into/array/345345/property:  excluded
// /path/to/one/array/0/element:      included
// /path/to/one/array/1/element:      excluded

This will exclude the given properties and all nested paths. Excluding / however will not work, since then you can just disable the plugin.

  • If a property is {} or undefined after processing all excludes statements, it will not be included in the patch.
  • Arrays work a little different. Since json-patch-operations work on the array index, array elements that are {} or undefined are still added to the patch. This brings support for later remove or replace operations to work as intended.
    The ARRAY_WILDCARD * matches every array element.

If there are any bugs experienced with the excludes feature please write an issue so we can fix it!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].