{"id":20433468,"url":"https://github.com/masquerade-circus/mongoose-history-plugin","last_synced_at":"2025-08-20T22:31:16.419Z","repository":{"id":20816161,"uuid":"90993713","full_name":"Masquerade-Circus/mongoose-history-plugin","owner":"Masquerade-Circus","description":"Mongoose plugin that saves history in JsonPatch format and SemVer format.","archived":false,"fork":false,"pushed_at":"2023-07-18T23:24:56.000Z","size":426,"stargazers_count":49,"open_issues_count":16,"forks_count":25,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-14T21:54:09.156Z","etag":null,"topics":["database","diff","document","eslint","history","json","jsonpatch","mongo","mongodb","mongoose","mongoose-history","mongoose-history-plugin","nosql","npm","patch","plugin","semantic","semver","version","versioning"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Masquerade-Circus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["Masquerade-Circus"],"custom":["https://www.paypal.me/masqueradecircus"]}},"created_at":"2017-05-11T15:16:32.000Z","updated_at":"2024-06-19T00:00:00.948Z","dependencies_parsed_at":"2024-06-18T23:59:54.131Z","dependency_job_id":"5ed96866-39e5-45c3-a5c8-42e2ed5f78ad","html_url":"https://github.com/Masquerade-Circus/mongoose-history-plugin","commit_stats":{"total_commits":41,"total_committers":10,"mean_commits":4.1,"dds":"0.29268292682926833","last_synced_commit":"15fea21cfd836bccf61a86bfdef217616c5691ec"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fmongoose-history-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fmongoose-history-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fmongoose-history-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fmongoose-history-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Masquerade-Circus","download_url":"https://codeload.github.com/Masquerade-Circus/mongoose-history-plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230462906,"owners_count":18229864,"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":["database","diff","document","eslint","history","json","jsonpatch","mongo","mongodb","mongoose","mongoose-history","mongoose-history-plugin","nosql","npm","patch","plugin","semantic","semver","version","versioning"],"created_at":"2024-11-15T08:19:18.734Z","updated_at":"2024-12-19T16:10:58.720Z","avatar_url":"https://github.com/Masquerade-Circus.png","language":"JavaScript","funding_links":["https://github.com/sponsors/Masquerade-Circus","https://www.paypal.me/masqueradecircus"],"categories":[],"sub_categories":[],"readme":"[![npm version](https://img.shields.io/npm/v/mongoose-history-plugin.svg?style=flat)](https://npmjs.org/package/mongoose-history-plugin \"View this project on npm\")\n[![Build Status](https://travis-ci.org/Masquerade-Circus/mongoose-history-plugin.svg?branch=master)](https://travis-ci.org/Masquerade-Circus/mongoose-history-plugin)\n[![Dependencies](https://img.shields.io/david/masquerade-circus/mongoose-history-plugin.svg?style=flat)](https://david-dm.org/masquerade-circus/mongoose-history-plugin)\n![](https://img.shields.io/github/issues/masquerade-circus/mongoose-history-plugin.svg)\n![](https://img.shields.io/snyk/vulnerabilities/npm/mongoose-history-plugin.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/521f72fc6d61426783692b62d64a3643)](https://www.codacy.com/app/Masquerade-Circus/mongoose-history-plugin?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=Masquerade-Circus/mongoose-history-plugin\u0026utm_campaign=Badge_Grade)\n[![Maintainability](https://api.codeclimate.com/v1/badges/c1263dd7fb4f90194625/maintainability)](https://codeclimate.com/github/Masquerade-Circus/mongoose-history-plugin/maintainability)\n[![Coverage Status](https://coveralls.io/repos/github/Masquerade-Circus/mongoose-history-plugin/badge.svg?branch=master)](https://coveralls.io/github/Masquerade-Circus/mongoose-history-plugin?branch=master)\n[![License](https://img.shields.io/github/license/masquerade-circus/mongoose-history-plugin.svg)](https://github.com/masquerade-circus/mongoose-history-plugin/blob/master/LICENSE)\n\n# mongoose-history-plugin\n\nMongoose plugin that saves documents history in [JsonPatch](http://jsonpatch.com/) format and [SemVer](http://semver.org/) format.\n\n## Table of Contents\n\n-   [Features](#features)\n\n-   [Install](#install)\n\n-   [Use](#use)\n\n-   [Document Methods](#document-methods)\n\n    -   [document.getDiffs(findQuery)](#documentgetdiffsfindquery)\n    -   [document.getDiff(version)](#documentgetdiffversion)\n    -   [document.getVersions(findQuery)](#documentgetversionsfindquery)\n    -   [document.getVersion(version)](#documentgetversionversion)\n    -   [document.compareVersions(versionLeft, versionRight)](#documentcompareversionsversionleft-versionright)\n\n-   [Tests](#tests)\n\n-   [Contributing](#contributing)\n\n-   [Legal](#legal)\n\n## Features\n\n-   Multiple history collections or one shared collection for the schemas\n-   Reference an account within the saved history\n-   Reference the user that performes the event within the saved history\n-   Save history for embedded documents\n-   Save history for populated fields\n-   Get diffs in JsonPatch format\n-   Get documents state for each version\n-   Compare two different versions\n\n## Install\n\nThis is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):\n\nIf using mongoose 4.x.x remove will only save if calling model.remove.\nMongoose 5.x now applies middleware hooks for remove on both schema and model.\n\nSee \u003chttps://mongoosejs.com/docs/middleware.html\u003e\n\n```bash\n$ npm install mongoose-history-plugin\n```\n\n## Use\n\n```javascript\nimport mongoose from 'mongoose';\nimport MongooseHistoryPlugin from 'mongoose-history-plugin';\n\nmongoose.connect('mongodb://localhost/Default');\n\n// Default options\nlet options = {\n  mongoose: mongoose, // A mongoose instance\n  connection: undefined, // DB connection to use instead of default connection\n  userCollection: 'users', // Colletcion to ref when you pass an user id\n  userCollectionIdType: false, // Type for user collection ref id, defaults to ObjectId\n  accountCollection: 'accounts', // Collection to ref when you pass an account id or the item has an account property\n  accountCollectionIdType: false, // Type for account collection ref id, defaults to ObjectId\n  userFieldName: 'user', // Name of the property for the user\n  accountFieldName: 'account', // Name of the property of the account if any\n  timestampFieldName: 'timestamp', // Name of the property of the timestamp\n  methodFieldName: 'method', // Name of the property of the method\n  collectionIdType: false, // Cast type for _id (support for other binary types like uuid) defaults to ObjectId\n  ignore: [], // List of fields to ignore when compare changes\n  noDiffSave: false, // If true save event even if there are no changes\n  noDiffSaveOnMethods: ['delete'], // If a method is in this list, it saves history even if there is no diff.\n  noEventSave: true, // If false save only when __history property is passed\n  modelName: '__histories', // Name of the collection for the histories\n  embeddedDocument: false, // Is this a sub document\n  embeddedModelName: '', // Name of model if used with embedded document\n\n  // If true save only the _id of the populated fields\n  // If false save the whole object of the populated fields\n  // If false and a populated field property changes it triggers a new history\n  // You need to populate the field after a change is made on the original document or it will not catch the differences\n  ignorePopulatedFields: true\n};\n\n// Add the plugin to the schema with default options\nlet Schema = mongoose.Schema({ name: 'string', size: 'string' });\nSchema.plugin(MongooseHistoryPlugin(options));\n\n// Create a model\nlet Tank = mongoose.model('tank', Schema);\n\n// Create a document\nlet small = new Tank({\n  size: 'small',\n  // History property is optional by default\n  __history: {\n    event: 'created',\n    user: undefined, // An object id of the user that generate the event\n    reason: undefined,\n    data: undefined, // Additional data to save with the event\n    type: undefined, // One of 'patch', 'minor', 'major'. If undefined defaults to 'major'\n    method: 'newTank' // Optional and intended for method reference\n  }\n});\nsmall\n  .save()\n  .then((small) =\u003e {\n    small.name = 'Small tank';\n\n    // History property is optional by default\n    small.__history = {\n      event: 'updated',\n      user: undefined,\n      reason: undefined,\n      data: undefined,\n      type: undefined,\n      method: 'updateTank'\n    };\n\n    return small.save();\n  })\n  .then((small) =\u003e {\n    // Create another history version\n    small.name = 'Smallest tank';\n\n    // History property is optional by default\n    small.__history = {\n      event: 'updated',\n      user: undefined,\n      reason: undefined,\n      data: undefined,\n      type: undefined,\n      method: 'updateTank'\n    };\n\n    return small.save();\n  })\n  .then((small) =\u003e {\n    // All options are optional\n    let query = {\n      find: {}, // Must be an object\n      select: {}, // Must be an object\n      sort: '',\n      populate: '',\n      limit: 20\n    };\n\n    // Get the diff histories in JsonDiffPatch format\n    small.getDiffs(query).then(console.log);\n    /*\n    [ \n      { \n        version: '2.0.0',\n        diff: { name: ['Small tank', 'Smallest tank'] },\n        event: 'updated',\n        method: 'updateTank',\n        timestamp: 2019-08-24T12:04:15.253Z },\n      { \n        version: '1.0.0',\n        diff: { name: [ 'Small tank' ] },\n        event: 'updated',\n        method: 'updateTank',\n        timestamp: 2019-08-24T12:04:15.253Z },\n      { \n        version: '0.0.0',\n        diff: { _id: [ '5d6127bf3a50db72bc8cbed2' ], size: [ 'small' ] },\n        event: 'created',\n        method: 'newTank',\n        timestamp: 2019-08-24T12:04:15.157Z \n      } \n    ]\n    */\n\n    // Get a diff history in JsonDiffPatch format\n    small.getDiff('1.0.0').then(console.log);\n\n    /*\n    { \n      _id: 5d6127bf3a50db72bc8cbed4,\n      version: '1.0.0',\n      collectionName: 'tank6',\n      collectionId: 5d6127bf3a50db72bc8cbed2,\n      diff: { name: [ 'Small tank' ] },\n      event: 'updated',\n      method: 'updateTank',\n      timestamp: 2019-08-24T12:04:15.253Z \n    }\n    */\n\n    // Get the versions\n    small.getVersions(query).then(console.log);\n    /*\n    [ \n      {\n        version: '2.0.0',\n        event: 'updated',\n        method: 'updateTank',\n        timestamp: expect.any(Date),\n        object: { name: 'Smallest tank' }\n      },\n      { \n        version: '1.0.0',\n        event: 'updated',\n        method: 'updateTank',\n        timestamp: 2019-08-24T12:04:15.253Z,\n        object: { name: 'Small tank' } \n      },\n      { \n        version: '0.0.0',\n        event: 'created',\n        method: 'newTank',\n        timestamp: 2019-08-24T12:04:15.157Z,\n        object: { \n          name: 'Small tank',\n          _id: '5d6127bf3a50db72bc8cbed2',\n          size: 'small' \n        } \n      } \n    ]\n    */\n\n    // Get a version\n    small.getVersion('1.0.0').then(console.log);\n    /*\n    { \n      _id: 5d6127bf3a50db72bc8cbed4,\n      version: '1.0.0',\n      collectionName: 'tank6',\n      collectionId: 5d6127bf3a50db72bc8cbed2,\n      event: 'updated',\n      method: 'updateTank',\n      timestamp: 2019-08-24T12:04:15.253Z,\n      object: { \n        _id: '5d6127bf3a50db72bc8cbed2',\n        size: 'small',\n        name: 'Small tank' \n      } \n    }\n    */\n\n    // Compare two versions\n    small.compareVersions('0.0.0', '1.0.0').then(console.log);\n    /*\n    { \n      diff: { name: [ 'Small tank' ] },\n      left: { _id: '5d6127bf3a50db72bc8cbed2', size: 'small' },\n      right: { \n        _id: '5d6127bf3a50db72bc8cbed2',\n        size: 'small',\n        name: 'Small tank' \n      } \n    }\n    */\n  });\n\nsmall\n  .remove()\n  .then((small) =\u003e {\n    small.__history = {\n      event: 'removed',\n      user: undefined,\n      reason: undefined,\n      data: undefined,\n      type: undefined,\n      method: 'delete'\n    };\n\n    return small.remove();\n  })\n  .then((small) =\u003e {\n    // All options are optional\n    let query = {\n      find: {}, // Must be an object\n      select: {}, // Must be an object\n      sort: '',\n      populate: '',\n      limit: 20\n    };\n\n    // Get the diff histories in JsonDiffPatch format\n    small.getDiffs(query).then(console.log);\n\n    // Get a diff history in JsonDiffPatch format\n    small.getDiff('2.0.0').then(console.log);\n\n    // Get the versions\n    small.getVersions(query).then(console.log);\n\n    // Get a version\n    small.getVersion('2.0.0').then(console.log);\n\n    // Compare two versions\n    // In the case of delete, the diff is empty because the object is not changed.\n    small.compareVersions('1.0.0', '2.0.0').then(console.log);\n  });\n\n// Add the plugin to many schemas with a single history collection\nlet plugin = MongooseHistoryPlugin(options);\nSchema.plugin(plugin);\nAnotherSchema.plugin(plugin);\n\n// Add the plugin with a dedicated history collection for every schema\nSchema.plugin(\n  MongooseHistoryPlugin(\n    Object.assign({}, options, { modelName: 'collectionName_versions' })\n  )\n);\nAnotherSchema.plugin(\n  MongooseHistoryPlugin(\n    Object.assign({}, options, { modelName: 'anotherCollectionName_versions' })\n  )\n);\n```\n\n## Document Methods\n\n### document.getDiffs([findQuery])\n\n  Returns an array of all the histories of the document. You can pass a options object that will be passed to a collection find method.\n\n  The returned objects within the array have the next shape: \n\n```javascript\n  { \n    version, // The version of the document according to the SemVer format \n    diff, // Changes made in this version diffed against the previous version and according to the JsonPatch format\n    event, // The event that create this version if any\n    method, // The name of the method that create this version if any\n    timestamp // The timestamp in which this version was created\n  }\n```\n\n### document.getDiff(version)\n\n  Returns the version history for this document. \n\n  The returned object has the next shape: \n\n```javascript\n  { \n    _id, // ObjectId for this history\n    version, // The version of the document according to the SemVer format \n    collectionName, // Name of the collection that belongs to this document\n    collectionId, // ObjectId of the document\n    diff, // Changes made in this version diffed against the previous version and according to the JsonPatch format\n    event, // The event that create this version if any\n    method, // The name of the method that create this version if any\n    timestamp // The timestamp in which this version was created\n  }\n```\n\n### document.getVersions([findQuery])\n\n  Returns an array of all the versions of the document. You can pass a options object that will be passed to a collection find method.\n\n  The returned objects within the array have the next shape: \n\n```javascript\n{ \n  version, // The version of the document according to the SemVer format \n  event, // The event that create this version if any\n  method, // The name of the method that create this version if any\n  timestamp // The timestamp in which this version was created\n  object // Object with the properties changed in this version diffed against the previous version \n}\n```\n\n### document.getVersion(version)\n\n  Returns the document as it was at the time of this version.\n\n  The returned object has the next shape: \n\n```javascript\n  { \n    _id, // ObjectId for this history\n    version, // The version of the document according to the SemVer format \n    collectionName, // Name of the collection that belongs to this document\n    collectionId, // ObjectId of the document\n    event, // The event that create this version if any\n    method, // The name of the method that create this version if any\n    timestamp, // The timestamp in which this version was created\n    object // The complete object as it was at this version\n  }\n```\n\n### document.compareVersions(versionLeft, versionRight)\n\n  Returns the differences between two versions of the document.\n\n  The returned object has the next shape:\n\n```javascript\n{ \n  diff, // The differences between the two versions according to the JsonPatch format\n  left, // The document as it was at the left version\n  right // The document as it was at the right version\n}\n```\n\n## Tests\n\n`npm test`\n\nFor development use `npm dev:test`\n\n## Contributing\n\n-   Use prettify and eslint to lint your code.\n-   Add tests for any new or changed functionality.\n-   Update the readme with an example if you add or change any functionality.\n\n## Legal\n\nAuthor: [Masquerade Circus](http://masquerade-circus.net). License [Apache-2.0](https://opensource.org/licenses/Apache-2.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasquerade-circus%2Fmongoose-history-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmasquerade-circus%2Fmongoose-history-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasquerade-circus%2Fmongoose-history-plugin/lists"}