{"id":19639570,"url":"https://github.com/codepunkt/mongoose-patch-history","last_synced_at":"2025-04-28T10:31:12.109Z","repository":{"id":8341331,"uuid":"58056959","full_name":"codepunkt/mongoose-patch-history","owner":"codepunkt","description":"Mongoose plugin that saves a history of JSON patch operations for all documents belonging to a schema in an associated 'patches' collection","archived":false,"fork":false,"pushed_at":"2023-10-13T15:37:46.000Z","size":1383,"stargazers_count":96,"open_issues_count":20,"forks_count":21,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-21T12:23:07.821Z","etag":null,"topics":["changes","changeset","history","json","json-patch","mongodb","mongoose","schema"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codepunkt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-05-04T14:06:50.000Z","updated_at":"2024-04-16T10:31:37.000Z","dependencies_parsed_at":"2024-01-17T04:19:52.062Z","dependency_job_id":null,"html_url":"https://github.com/codepunkt/mongoose-patch-history","commit_stats":null,"previous_names":["gonsfx/mongoose-patch-history"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codepunkt%2Fmongoose-patch-history","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codepunkt%2Fmongoose-patch-history/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codepunkt%2Fmongoose-patch-history/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codepunkt%2Fmongoose-patch-history/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codepunkt","download_url":"https://codeload.github.com/codepunkt/mongoose-patch-history/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251038185,"owners_count":21526653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["changes","changeset","history","json","json-patch","mongodb","mongoose","schema"],"created_at":"2024-11-11T13:02:04.822Z","updated_at":"2025-04-28T10:31:12.101Z","avatar_url":"https://github.com/codepunkt.png","language":"JavaScript","readme":"\u003cp align=\"center\"\u003e\u003cimg title=\"redux-active\" src=\"docs/mongoose-patch-history.png\" width=\"519\" style=\"margin-top:20px;\"\u003e\u003c/p\u003e\n\n[![npm version](https://badge.fury.io/js/mongoose-patch-history.svg)](https://badge.fury.io/js/mongoose-patch-history) [![Build Status](https://travis-ci.org/codepunkt/mongoose-patch-history.svg?branch=master)](https://travis-ci.org/codepunkt/mongoose-patch-history) [![Greenkeeper badge](https://badges.greenkeeper.io/codepunkt/mongoose-patch-history.svg)](https://greenkeeper.io/) [![Known Vulnerabilities](https://snyk.io/test/github/codepunkt/mongoose-patch-history/badge.svg)](https://snyk.io/test/github/codepunkt/mongoose-patch-history:package.json?targetFile=package.json) [![Coverage Status](https://coveralls.io/repos/github/codepunkt/mongoose-patch-history/badge.svg?branch=master)](https://coveralls.io/github/codepunkt/mongoose-patch-history?branch=master)\n\nMongoose Patch History is a mongoose plugin that saves a history of [JSON Patch](http://jsonpatch.com/) operations for all documents belonging to a schema in an associated \"patches\" collection.\n\n## Installation\n\n    $ npm install mongoose-patch-history\n\n## Usage\n\nTo 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:\n\n```javascript\nimport mongoose, { Schema } from 'mongoose'\nimport patchHistory from 'mongoose-patch-history'\n\n/* or the following if not running your app with babel:\nconst patchHistory = require('mongoose-patch-history').default;\nconst mongoose = require('mongoose');\nconst Schema = mongoose.Schema;\n*/\n\nconst PostSchema = new Schema({\n  title: { type: String, required: true },\n  comments: Array,\n})\n\nPostSchema.plugin(patchHistory, { mongoose, name: 'postPatches' })\nconst Post = mongoose.model('Post', PostSchema)\n```\n\n**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.\n\n### Storing a new document\n\nContinuing the previous example, a new patch is added to the associated patch collection whenever a new post is added to the posts collection:\n\n```javascript\nPost.create({ title: 'JSON patches' })\n  .then(post =\u003e post.patches.findOne({ ref: post.id }))\n  .then(console.log)\n\n// {\n//   _id: ObjectId('4edd40c86762e0fb12000003'),\n//   ref: ObjectId('4edd40c86762e0fb12000004'),\n//   ops: [\n//     { value: 'JSON patches', path: '/title', op: 'add' },\n//     { value: [], path: '/comments', op: 'add' }\n//   ],\n//   date: new Date(1462360838107),\n//   __v: 0\n// }\n```\n\n### Updating an existing document\n\n**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:\n\n```javascript\nconst data = {\n  title: 'JSON patches with mongoose',\n  comments: [{ message: 'Wow! Such Mongoose! Very NoSQL!' }],\n}\n\nPost.create({ title: 'JSON patches' })\n  .then(post =\u003e post.set(data).save())\n  .then(post =\u003e post.patches.find({ ref: post.id }))\n  .then(console.log)\n\n// [{\n//   _id: ObjectId('4edd40c86762e0fb12000003'),\n//   ref: ObjectId('4edd40c86762e0fb12000004'),\n//   ops: [\n//     { value: 'JSON patches', path: '/title', op: 'add' },\n//     { value: [], path: '/comments', op: 'add' }\n//   ],\n//   date: new Date(1462360838107),\n//   __v: 0\n// }, {\n//   _id: ObjectId('4edd40c86762e0fb12000005'),\n//   ref: ObjectId('4edd40c86762e0fb12000004'),\n//   ops: [\n//     { value: { message: 'Wow! Such Mongoose! Very NoSQL!' }, path: '/comments/0', op: 'add' },\n//     { value: 'JSON patches with mongoose', path: '/title', op: 'replace' }\n//   ],\n//   \"date\": new Date(1462361848742),\n//   \"__v\": 0\n// }]\n```\n\n### Rollback to a specific patch\n\n```javascript\nrollback(ObjectId, data, save)\n```\n\nDocuments 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.\n\n```javascript\nPost.create({ title: 'First version' })\n  .then(post =\u003e post.set({ title: 'Second version' }).save())\n  .then(post =\u003e post.set({ title: 'Third version' }).save())\n  .then(post =\u003e {\n    return post.patches\n      .find({ ref: post.id })\n      .then(patches =\u003e post.rollback(patches[1].id))\n  })\n  .then(console.log)\n\n// {\n//   _id: ObjectId('4edd40c86762e0fb12000006'),\n//   title: 'Second version',\n//   __v: 0\n// }\n```\n\n#### Injecting data\n\nFurther the `rollback` method accepts a _data_ object which is injected into the document.\n\n```javascript\npost.rollback(patches[1].id, { name: 'merged' })\n\n// {\n//   _id: ObjectId('4edd40c86762e0fb12000006'),\n//   title: 'Second version',\n//   name: 'merged',\n//   __v: 0\n// }\n```\n\n#### Rollback without saving\n\nTo `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.\n\n```javascript\npost.rollback(patches[1].id, {}, false)\n\n// Returns the document without saving it back to the db.\n// {\n//   _id: ObjectId('4edd40c86762e0fb12000006'),\n//   title: 'Second version',\n//   __v: 0\n// }\n```\n\nThe `rollback` method will throw an Error when invoked with an ObjectId that is\n\n- not a patch of the document\n- the latest patch of the document\n\n## Options\n\n```javascript\nPostSchema.plugin(patchHistory, {\n  mongoose,\n  name: 'postPatches',\n})\n```\n\n- `mongoose` :pushpin: _required_ \u003cbr/\u003e\n  The mongoose instance to work with\n- `name` :pushpin: _required_ \u003cbr/\u003e\n  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\n- `removePatches` \u003cbr/\u003e\n  Removes patches when origin document is removed. Default: `true`\n- `transforms` \u003cbr/\u003e\n  An array of two functions that generate model and collection name based on the `name` option. Default: An array of [humps](https://github.com/domchristie/humps).pascalize and [humps](https://github.com/domchristie/humps).decamelize\n- `includes` \u003cbr/\u003e\n  Property definitions that will be included in the patch schema. Read more about includes in the next chapter of the documentation. Default: `{}`\n- `excludes` \u003cbr/\u003e\n  Property paths that will be excluded in patches. Read more about excludes in the [excludes chapter of the documentation](https://github.com/codepunkt/mongoose-patch-history#excludes). Default: `[]`\n- `trackOriginalValue` \u003cbr/\u003e\n  If enabled, the original value will be stored in the change patches under the attribute `originalValue`. Default: `false`\n\n### Includes\n\n```javascript\nPostSchema.plugin(patchHistory, {\n  mongoose,\n  name: 'postPatches',\n  includes: {\n    title: { type: String, required: true },\n  },\n})\n```\n\nThis 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.\n\n```javascript\nPost.create({ title: 'Included in every patch' })\n  .then((post) =\u003e post.patches.findOne({ ref: post.id })\n  .then((patch) =\u003e {\n    console.log(patch.title) // 'Included in every patch'\n  })\n```\n\nThe value of the patch documents properties is read from the versioned documents property of the same name.\n\n#### Reading from virtuals\n\nThere 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](http://mongoosejs.com/docs/guide.html#virtuals) on the versioned document and an additional `from` property in the include options of **mongoose-patch-history**:\n\n```javascript\n// save user as _user in versioned documents\nPostSchema.virtual('user').set(function (user) {\n  this._user = user\n})\n\n// read user from _user in patch documents\nPostSchema.plugin(patchHistory, {\n  mongoose,\n  name: 'postPatches',\n  includes: {\n    user: { type: Schema.Types.ObjectId, required: true, from: '_user' },\n  },\n})\n\n// create post, pass in user information\nPost.create({\n  title: 'Why is hiring broken?',\n  user: mongoose.Types.ObjectId(),\n})\n  .then(post =\u003e {\n    console.log(post.user) // undefined\n    return post.patches.findOne({ ref: post.id })\n  })\n  .then(patch =\u003e {\n    console.log(patch.user) // 4edd40c86762e0fb12000012\n  })\n```\n\nIn case of a rollback in this scenario, the `rollback` method accepts an [object as its second parameter](https://github.com/codepunkt/mongoose-patch-history#injecting-data) where additional data can be injected:\n\n```javascript\nPost.create({ title: 'v1', user: mongoose.Types.ObjectId() })\n  .then(post =\u003e\n    post\n      .set({\n        title: 'v2',\n        user: mongoose.Types.ObjectId(),\n      })\n      .save()\n  )\n  .then(post =\u003e {\n    return post.patches.find({ ref: post.id }).then(patches =\u003e\n      post.rollback(patches[0].id, {\n        user: mongoose.Types.ObjectId(),\n      })\n    )\n  })\n```\n\n#### Reading from query options\n\nIn situations where you are running Mongoose queries directly instead of via a document, you can specify the extra fields in the query options:\n\n```javascript\nPost.findOneAndUpdate(\n  { _id: '4edd40c86762e0fb12000012' },\n  { title: 'Why is hiring broken? (updated)' },\n  { _user: mongoose.Types.ObjectId() }\n)\n```\n\n### Excludes\n\n```javascript\nPostSchema.plugin(patchHistory, {\n  mongoose,\n  name: 'postPatches',\n  excludes: [\n    '/path/to/hidden/property',\n    '/path/into/array/*/property',\n    '/path/to/one/array/1/element',\n  ],\n})\n\n// Properties\n// /path/to/hidden:                   included\n// /path/to/hidden/property:          excluded\n// /path/to/hidden/property/nesting:  excluded\n\n// Array element properties\n// /path/into/array/0:                included\n// /path/into/array/345345/property:  excluded\n// /path/to/one/array/0/element:      included\n// /path/to/one/array/1/element:      excluded\n```\n\nThis will exclude the given properties and _all nested_ paths. Excluding `/` however will not work, since then you can just disable the plugin.\n\n- If a property is `{}` or `undefined` after processing all excludes statements, it will _not_ be included in the patch.\n- 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.\u003cbr/\u003e\n  The `ARRAY_WILDCARD` `*` matches every array element.\n\nIf there are any bugs experienced with the `excludes` feature please write an issue so we can fix it!\n","funding_links":[],"categories":["Packages","⏱ Timestamps \u0026 Audit"],"sub_categories":["Mongoose"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodepunkt%2Fmongoose-patch-history","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodepunkt%2Fmongoose-patch-history","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodepunkt%2Fmongoose-patch-history/lists"}