Meteor-Community-Packages / meteor-collection-hooks

Meteor Collection Hooks
https://atmospherejs.com/matb33/collection-hooks
MIT License
656 stars 90 forks source link
collections hacktoberfest hooks meteor mongodb

Meteor Collection Hooks

Test suite Code lint CodeQL Analysis

Extends Mongo.Collection with before/after hooks for insert, update, remove, find, and findOne.

Works 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).

Please refer to History.md for a summary of recent changes.

Getting Started

Installation:

meteor add matb33:collection-hooks

.before.insert(userId, doc)

Fired before the doc is inserted.

Allows you to modify doc as needed, or run additional functionality

import { Mongo } from 'meteor/mongo';
const test = new Mongo.Collection("test");

test.before.insert(function (userId, doc) {
  doc.createdAt = Date.now();
});

.before.update(userId, doc, fieldNames, modifier, options)

Fired before the doc is updated.

Allows you to to change the modifier as needed, or run additional functionality.

test.before.update(function (userId, doc, fieldNames, modifier, options) {
  modifier.$set = modifier.$set || {};
  modifier.$set.modifiedAt = Date.now();
});

Important:

  1. Note that we are changing modifier, and not doc. Changing doc won't have any effect as the document is a copy and is not what ultimately gets sent down to the underlying update method.

  2. When triggering a single update targeting multiple documents using the option multi: true (see Meteor documentation), 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.


.before.remove(userId, doc)

Fired just before the doc is removed.

Allows you to to affect your system while the document is still in existence -- useful for maintaining system integrity, such as cascading deletes.

test.before.remove(function (userId, doc) {
  // ...
});

.before.upsert(userId, selector, modifier, options)

Fired before the doc is upserted.

Allows you to to change the modifier as needed, or run additional functionality.

test.before.upsert(function (userId, selector, modifier, options) {
  modifier.$set = modifier.$set || {};
  modifier.$set.modifiedAt = Date.now();
});

Note that calling upsert will always fire .before.upsert hooks, but will call either .after.insert or .after.update hooks depending on the outcome of the upsert operation. There is no such thing as a .after.upsert hook at this time.


.after.insert(userId, doc)

Fired after the doc was inserted.

Allows you to run post-insert tasks, such as sending notifications of new document insertions.

test.after.insert(function (userId, doc) {
  // ...
});

.after.update(userId, doc, fieldNames, modifier, options)

Fired after the doc was updated.

Allows you to to run post-update tasks, potentially comparing the previous and new documents to take further action.

test.after.update(function (userId, doc, fieldNames, modifier, options) {
  // ...
}, {fetchPrevious: true/false});

Important: If you have multiple hooks defined, and at least one of them does not specify fetchPrevious: false, then the documents will be fetched and provided as this.previous to all hook callbacks. All after-update hooks for the same collection must have fetchPrevious: false set in order to effectively disable the pre-fetching of documents.

It is instead recommended to use the collection-wide options (e.g. MyCollection.hookOptions.after.update = {fetchPrevious: false};).

This 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).


.after.remove(userId, doc)

Fired after the doc was removed.

doc contains a copy of the document before it was removed.

Allows you to run post-removal tasks that don't necessarily depend on the document being found in the database (external service clean-up for instance).

test.after.remove(function (userId, doc) {
  // ...
});

.before.find(userId, selector, options)

Fired before a find query.

Allows you to adjust selector/options on-the-fly.

test.before.find(function (userId, selector, options) {
  // ...
});

Important: This hook does not get called for after.update hooks (see https://github.com/Meteor-Community-Packages/meteor-collection-hooks/pull/297).


.after.find(userId, selector, options, cursor)

Fired after a find query.

Allows you to act on a given find query. The cursor resulting from the query is provided as the last argument for convenience.

test.after.find(function (userId, selector, options, cursor) {
  // ...
});

.before.findOne(userId, selector, options)

Fired before a findOne query.

Allows you to adjust selector/options on-the-fly.

test.before.findOne(function (userId, selector, options) {
  // ...
});

.after.findOne(userId, selector, options, doc)

Fired after a findOne query.

Allows you to act on a given findOne query. The document resulting from the query is provided as the last argument for convenience.

test.after.findOne(function (userId, selector, options, doc) {
  // ...
});

Direct access (circumventing hooks)

All compatible methods have a direct version that circumvent any defined hooks. For example:

collection.direct.insert({_id: "test", test: 1});
collection.direct.insertAsync({_id: "test", test: 1});
collection.direct.upsert({_id: "test", test: 1});
collection.direct.upsertAsync({_id: "test", test: 1});
collection.direct.update({_id: "test"}, {$set: {test: 1}});
collection.direct.updateAsync({_id: "test"}, {$set: {test: 1}});
collection.direct.find({test: 1});
collection.direct.findOne({test: 1});
collection.direct.findOneAsync({test: 1});
collection.direct.remove({_id: "test"});
collection.direct.removeAsync({_id: "test"});

Default options

As of version 0.7.0, options can be passed to hook definitions. Default options can be specified globally and on a per-collection basis for all or some hooks, with more specific ones having higher specificity.

Examples (in order of least specific to most specific):

import { CollectionHooks } from 'meteor/matb33:collection-hooks';

CollectionHooks.defaults.all.all = {exampleOption: 1};

CollectionHooks.defaults.before.all = {exampleOption: 2};
CollectionHooks.defaults.after.all = {exampleOption: 3};

CollectionHooks.defaults.all.update = {exampleOption: 4};
CollectionHooks.defaults.all.remove = {exampleOption: 5};

CollectionHooks.defaults.before.insert = {exampleOption: 6};
CollectionHooks.defaults.after.remove = {exampleOption: 7};

Similarly, collection-wide options can be defined (these have a higher specificity than the global defaults from above):

import { Mongo } from 'meteor/mongo';
const testCollection = new Mongo.Collection("test");

testCollection.hookOptions.all.all = {exampleOption: 1};

testCollection.hookOptions.before.all = {exampleOption: 2};
testCollection.hookOptions.after.all = {exampleOption: 3};

testCollection.hookOptions.all.update = {exampleOption: 4};
testCollection.hookOptions.all.remove = {exampleOption: 5};

testCollection.hookOptions.before.insert = {exampleOption: 6};
testCollection.hookOptions.after.remove = {exampleOption: 7};

Currently (as of 0.7.0), only fetchPrevious is implemented as an option, and is only relevant to after-update hooks.


Additional notes


Maintainers

Maintained by Meteor Community Packages and in particular by:

Contributors