{"id":13763750,"url":"https://github.com/Meteor-Community-Packages/meteor-collection-hooks","last_synced_at":"2025-05-10T17:30:55.570Z","repository":{"id":8737721,"uuid":"10413561","full_name":"Meteor-Community-Packages/meteor-collection-hooks","owner":"Meteor-Community-Packages","description":"Meteor Collection Hooks","archived":false,"fork":false,"pushed_at":"2024-12-06T12:56:00.000Z","size":893,"stargazers_count":657,"open_issues_count":48,"forks_count":92,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-05-07T17:05:34.717Z","etag":null,"topics":["collections","hacktoberfest","hooks","meteor","mongodb"],"latest_commit_sha":null,"homepage":"https://atmospherejs.com/matb33/collection-hooks","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/Meteor-Community-Packages.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"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,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["storytellercz","jankapunkt"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2013-05-31T21:43:23.000Z","updated_at":"2024-12-06T12:56:04.000Z","dependencies_parsed_at":"2023-11-12T22:28:23.293Z","dependency_job_id":"5ee53bd9-985f-465a-bfaf-4c98a39247e0","html_url":"https://github.com/Meteor-Community-Packages/meteor-collection-hooks","commit_stats":{"total_commits":382,"total_committers":35,"mean_commits":"10.914285714285715","dds":0.6570680628272252,"last_synced_commit":"8b5b0015c21266d03fc74df80f0ec3f319046f18"},"previous_names":["matb33/meteor-collection-hooks"],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-collection-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-collection-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-collection-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-collection-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Meteor-Community-Packages","download_url":"https://codeload.github.com/Meteor-Community-Packages/meteor-collection-hooks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253453206,"owners_count":21911059,"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":["collections","hacktoberfest","hooks","meteor","mongodb"],"created_at":"2024-08-03T15:00:57.728Z","updated_at":"2025-05-10T17:30:55.564Z","avatar_url":"https://github.com/Meteor-Community-Packages.png","language":"JavaScript","funding_links":["https://github.com/sponsors/storytellercz","https://github.com/sponsors/jankapunkt"],"categories":["Collections","Packages"],"sub_categories":[],"readme":"# Meteor Collection Hooks\n\n![Test suite](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/workflows/Test%20suite/badge.svg)\n![Code lint](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/workflows/Code%20lint/badge.svg)\n![CodeQL Analysis](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/workflows/CodeQL/badge.svg)\n\n\nExtends Mongo.Collection with `before`/`after` hooks for `insert`, `update`, `remove`, `find`, and `findOne`.\n\nWorks across client, server or a mix. Also works when a client initiates a collection method and the server runs the hook, all while respecting the collection validators (allow/deny).\n\nPlease refer to [History.md](History.md) for a summary of recent changes.\n\n## Getting Started\n\nInstallation:\n\n```\nmeteor add matb33:collection-hooks\n```\n\n--------------------------------------------------------------------------------\n\n### .before.insert(userId, doc)\n\nFired before the doc is inserted.\n\nAllows you to modify doc as needed, or run additional\nfunctionality\n\n- `this.transform()` obtains transformed version of document, if a transform was\ndefined.\n\n```javascript\nimport { Mongo } from 'meteor/mongo';\nconst test = new Mongo.Collection(\"test\");\n\ntest.before.insert(function (userId, doc) {\n  doc.createdAt = Date.now();\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .before.update(userId, doc, fieldNames, modifier, options)\n\nFired before the doc is updated.\n\nAllows you to to change the `modifier` as needed, or run additional\nfunctionality.\n\n- `this.transform()` obtains transformed version of document, if a transform was\ndefined.\n\n```javascript\ntest.before.update(function (userId, doc, fieldNames, modifier, options) {\n  modifier.$set = modifier.$set || {};\n  modifier.$set.modifiedAt = Date.now();\n});\n```\n\n__Important__: \n\n1. Note that we are changing `modifier`, and not `doc`.\nChanging `doc` won't have any effect as the document is a copy and is not what\nultimately gets sent down to the underlying `update` method.\n\n2. When triggering a single update targeting multiple documents using the option `multi: true` (see [Meteor documentation](https://docs.meteor.com/api/collections.html#Mongo-Collection-update)), the `before.update` hook is called once per document about to be updated, **but** the collection update called afterwards remains a single update (targetting multiple documents) with a single modifier. Hence it is not possible at the time to use `before.update` to create a specific modifier for each targeted document.\n\n--------------------------------------------------------------------------------\n\n### .before.remove(userId, doc)\n\nFired just before the doc is removed.\n\nAllows you to to affect your system while the document is still in\nexistence -- useful for maintaining system integrity, such as cascading deletes.\n\n- `this.transform()` obtains transformed version of document, if a transform was\ndefined.\n\n```javascript\ntest.before.remove(function (userId, doc) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .before.upsert(userId, selector, modifier, options)\n\nFired before the doc is upserted.\n\nAllows you to to change the `modifier` as needed, or run additional\nfunctionality.\n\n```javascript\ntest.before.upsert(function (userId, selector, modifier, options) {\n  modifier.$set = modifier.$set || {};\n  modifier.$set.modifiedAt = Date.now();\n});\n```\n\nNote that calling `upsert` will always fire `.before.upsert` hooks, but will\ncall either `.after.insert` or `.after.update` hooks depending on the outcome of\nthe `upsert` operation. There is no such thing as a `.after.upsert` hook at this\ntime.\n\n--------------------------------------------------------------------------------\n\n### .after.insert(userId, doc)\n\nFired after the doc was inserted.\n\nAllows you to run post-insert tasks, such as sending notifications\nof new document insertions.\n\n- `this.transform()` obtains transformed version of document, if a transform was\ndefined;\n- `this._id` holds the newly inserted `_id` if available.\n\n```javascript\ntest.after.insert(function (userId, doc) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .after.update(userId, doc, fieldNames, modifier, options)\n\nFired after the doc was updated.\n\nAllows you to to run post-update tasks, potentially comparing the\nprevious and new documents to take further action.\n\n- `this.previous` contains the document before it was updated.\n  - The optional `fetchPrevious` option, when set to false, will not fetch\n    documents before running the hooks. `this.previous` will then not be\n    available. The default behavior is to fetch the documents.\n- `this.transform()` obtains transformed version of document, if a transform was\n  defined. Note that this function accepts an optional parameter to specify the\n  document to transform — useful to transform previous:\n  `this.transform(this.previous)`.\n\n```javascript\ntest.after.update(function (userId, doc, fieldNames, modifier, options) {\n  // ...\n}, {fetchPrevious: true/false});\n```\n\n__Important:__ If you have multiple hooks defined, and at least one of them does\n*not* specify `fetchPrevious: false`, then the documents *will* be fetched\nand provided as `this.previous` to all hook callbacks. All after-update hooks\nfor the same collection must have `fetchPrevious: false` set in order to\neffectively disable the pre-fetching of documents.\n\nIt is instead recommended to use the collection-wide options (e.g.\n`MyCollection.hookOptions.after.update = {fetchPrevious: false};`).\n\nThis hook will always be called with the new documents; even if the updated document gets modified in a way were it would normally not be able to be found because of `before.find` hooks (see https://github.com/Meteor-Community-Packages/meteor-collection-hooks/pull/297).\n\n--------------------------------------------------------------------------------\n\n### .after.remove(userId, doc)\n\nFired after the doc was removed.\n\n`doc` contains a copy of the document before it was removed.\n\nAllows you to run post-removal tasks that don't necessarily depend\non the document being found in the database (external service clean-up for\ninstance).\n\n- `this.transform()` obtains transformed version of document, if a transform was\ndefined.\n\n```javascript\ntest.after.remove(function (userId, doc) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .before.find(userId, selector, options)\n\nFired before a find query.\n\nAllows you to adjust selector/options on-the-fly.\n\n```javascript\ntest.before.find(function (userId, selector, options) {\n  // ...\n});\n```\n\n__Important:__ \n- The function used as `before.find` hook cannot be async\n- This hook does not get called for `after.update` hooks (see https://github.com/Meteor-Community-Packages/meteor-collection-hooks/pull/297).\n\n--------------------------------------------------------------------------------\n\n### .after.find(userId, selector, options, cursor)\n\nFired after a find query.\n\nAllows you to act on a given find query. The cursor resulting from\nthe query is provided as the last argument for convenience.\n\n```javascript\ntest.after.find(function (userId, selector, options, cursor) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .before.findOne(userId, selector, options)\n\nFired before a findOne query.\n\nAllows you to adjust selector/options on-the-fly.\n\n```javascript\ntest.before.findOne(function (userId, selector, options) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n### .after.findOne(userId, selector, options, doc)\n\nFired after a findOne query.\n\nAllows you to act on a given findOne query. The document resulting\nfrom the query is provided as the last argument for convenience.\n\n```javascript\ntest.after.findOne(function (userId, selector, options, doc) {\n  // ...\n});\n```\n\n--------------------------------------------------------------------------------\n\n## Direct access (circumventing hooks)\n\nAll compatible methods have a `direct` version that circumvent any defined hooks. For example:\n\n```javascript\ncollection.direct.insert({_id: \"test\", test: 1});\ncollection.direct.insertAsync({_id: \"test\", test: 1});\ncollection.direct.upsert({_id: \"test\", test: 1});\ncollection.direct.upsertAsync({_id: \"test\", test: 1});\ncollection.direct.update({_id: \"test\"}, {$set: {test: 1}});\ncollection.direct.updateAsync({_id: \"test\"}, {$set: {test: 1}});\ncollection.direct.find({test: 1});\ncollection.direct.findOne({test: 1});\ncollection.direct.findOneAsync({test: 1});\ncollection.direct.remove({_id: \"test\"});\ncollection.direct.removeAsync({_id: \"test\"});\n```\n\n--------------------------------------------------------------------------------\n\n## Default options\n\nAs of version 0.7.0, options can be passed to hook definitions. Default options\ncan be specified globally and on a per-collection basis for all or some hooks,\nwith more specific ones having higher specificity.\n\nExamples (in order of least specific to most specific):\n\n```javascript\nimport { CollectionHooks } from 'meteor/matb33:collection-hooks';\n\nCollectionHooks.defaults.all.all = {exampleOption: 1};\n\nCollectionHooks.defaults.before.all = {exampleOption: 2};\nCollectionHooks.defaults.after.all = {exampleOption: 3};\n\nCollectionHooks.defaults.all.update = {exampleOption: 4};\nCollectionHooks.defaults.all.remove = {exampleOption: 5};\n\nCollectionHooks.defaults.before.insert = {exampleOption: 6};\nCollectionHooks.defaults.after.remove = {exampleOption: 7};\n```\n\nSimilarly, collection-wide options can be defined (these have a higher\nspecificity than the global defaults from above):\n\n```javascript\nimport { Mongo } from 'meteor/mongo';\nconst testCollection = new Mongo.Collection(\"test\");\n\ntestCollection.hookOptions.all.all = {exampleOption: 1};\n\ntestCollection.hookOptions.before.all = {exampleOption: 2};\ntestCollection.hookOptions.after.all = {exampleOption: 3};\n\ntestCollection.hookOptions.all.update = {exampleOption: 4};\ntestCollection.hookOptions.all.remove = {exampleOption: 5};\n\ntestCollection.hookOptions.before.insert = {exampleOption: 6};\ntestCollection.hookOptions.after.remove = {exampleOption: 7};\n```\n\n_Currently (as of 0.7.0), only `fetchPrevious` is implemented as an option, and\nis only relevant to after-update hooks._\n\n--------------------------------------------------------------------------------\n\n## Additional notes\n\n- Returning `false` in any `before` hook will prevent the underlying method (and\nsubsequent `after` hooks) from executing. Note that all `before` hooks will\nstill continue to run even if the first hook returns `false`.\n\n- ~~If you wish to make `userId` available to a `find` query in a `publish`\nfunction, try the technique detailed in this [comment](https://github.com/matb33/meteor-collection-hooks/issues/7#issuecomment-24021616)~~ `userId` is available to `find` and `findOne` queries that were invoked within a `publish` function.\n\n- All hook callbacks have `this._super` available to them (the underlying\nmethod) as well as `this.context`, the equivalent of `this` to the underlying\nmethod. Additionally, `this.args` contain the original arguments passed to the\nmethod and can be modified by reference (for example, modifying a selector in a\n`before` hook so that the underlying method uses this new selector).\n\n- It is quite normal for `userId` to sometimes be unavailable to hook callbacks\nin some circumstances. For example, if an `update` is fired from the server\nwith no user context, the server certainly won't be able to provide any\nparticular userId.\n\n- You can define a `defaultUserId` in case you want to pass an userId to the hooks but there is no context. For instance if you are executing and API endpoint where the `userId` is derived from a token. Just assign the userId to `CollectionHooks.defaultUserId`. It will be overriden by the userId of the context if it exists.\n\n- If, like me, you transform `Meteor.users` through a [round-about way](https://github.com/matb33/meteor-collection-hooks/issues/15#issuecomment-25809919) involving\n`find` and `findOne`, then you won't be able to use `this.transform()`. Instead,\ngrab the transformed user with `findOne`.\n\n- When adding a hook, a handler object is returned with these methods:\n  - `remove()`: will remove that particular hook;\n  - `replace(callback, options)`: will replace the hook callback and options.\n\n- If your hook is defined in common code (both server and client), it will run\ntwice: once on the server and once on the client. If your intention is for the\nhook to run only once, make sure the hook is defined somewhere where only either\nthe client or the server reads it. *When in doubt, define your hooks on the\nserver.*\n\n- Both `update` and `remove` internally make use of `find`, so be aware that\n`find`/`findOne` hooks can fire for those methods.\n\n- `find` hooks are also fired when fetching documents for `update`, `upsert` and `remove` hooks.\n\n- If using the `direct` version to bypass a hook, any mongo operations done within nested \ncallbacks of the `direct` operation will also by default run as `direct`. You can use the following\nline in a nested callback before the operation to unset the `direct` setting: \n`CollectionHooks.directEnv = new Meteor.EnvironmentVariable(false)`\n\n--------------------------------------------------------------------------------\n\n## Maintainers\nMaintained by [Meteor Community Packages](https://github.com/Meteor-Community-Packages) and in particular by:\n\n- Mathieu Bouchard ([matb33](https://github.com/matb33))\n- Andrew Mao ([mizzao](https://github.com/mizzao))\n- Simon Fridlund ([zimme](https://github.com/zimme))\n- Jan Dvorak ([StorytellerCZ](https://github.com/StorytellerCZ))\n\n## Contributors\n\n- Eric Dobbertin ([aldeed](https://github.com/aldeed))\n- Kevin Kaland ([wizonesolutions](https://github.com/wizonesolutions))\n- Jonathan James ([jonjamz](https://github.com/jonjamz))\n- Dave Workman ([davidworkman9](https://github.com/davidworkman9))\n- Tarang Patel ([Tarangp](https://github.com/Tarangp))\n- Nathan Strauser ([nate-strauser](https://github.com/nate-strauser))\n- Hubert OG ([subhog](https://github.com/subhog))\n- Richard Lai ([rclai](https://github.com/rclai))\n- Sahebjot Singh ([raunaqrox](https://github.com/raunaqrox))\n- Aram Kocharyan ([aramk](https://github.com/aramk))\n- Pierre Ozoux ([pierreozoux](https://github.com/pierreozoux))\n- Tom Coleman ([tmeasday](https://github.com/tmeasday))\n- Eric Jackson ([repjackson](https://github.com/repjackson))\n- Koen Lav ([KoenLav](https://github.com/KoenLav))\n- Chris Pravetz ([cpravetz](https://github.com/cpravetz))\n- Jan Kuster ([jankapunkt](https://github.com/jankapunkt))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-collection-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-collection-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-collection-hooks/lists"}