• Stars
    star
    657
  • Rank 68,589 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 11 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Meteor Collection Hooks

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

  • this.transform() obtains transformed version of document, if a transform was defined.
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.

  • this.transform() obtains transformed version of document, if a transform was defined.
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.

  • this.transform() obtains transformed version of document, if a transform was defined.
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.

  • this.transform() obtains transformed version of document, if a transform was defined;
  • this._id holds the newly inserted _id if available.
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.

  • this.previous contains the document before it was updated.
    • The optional fetchPrevious option, when set to false, will not fetch documents before running the hooks. this.previous will then not be available. The default behavior is to fetch the documents.
  • this.transform() obtains transformed version of document, if a transform was defined. Note that this function accepts an optional parameter to specify the document to transform β€” useful to transform previous: this.transform(this.previous).
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 #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).

  • this.transform() obtains transformed version of document, if a transform was defined.
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 #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.update({_id: "test"}, {$set: {test: 1}});
collection.direct.find({test: 1});
collection.direct.findOne({test: 1});
collection.direct.remove({_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

  • Returning false in any before hook will prevent the underlying method (and subsequent after hooks) from executing. Note that all before hooks will still continue to run even if the first hook returns false.

  • If you wish to make userId available to a find query in a publish function, try the technique detailed in this comment userId is available to find and findOne queries that were invoked within a publish function.

  • All hook callbacks have this._super available to them (the underlying method) as well as this.context, the equivalent of this to the underlying method. Additionally, this.args contain the original arguments passed to the method and can be modified by reference (for example, modifying a selector in a before hook so that the underlying method uses this new selector).

  • It is quite normal for userId to sometimes be unavailable to hook callbacks in some circumstances. For example, if an update is fired from the server with no user context, the server certainly won't be able to provide any particular userId.

  • 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.

  • If, like me, you transform Meteor.users through a round-about way involving find and findOne, then you won't be able to use this.transform(). Instead, grab the transformed user with findOne.

  • When adding a hook, a handler object is returned with these methods:

    • remove(): will remove that particular hook;
    • replace(callback, options): will replace the hook callback and options.
  • If your hook is defined in common code (both server and client), it will run twice: once on the server and once on the client. If your intention is for the hook to run only once, make sure the hook is defined somewhere where only either the client or the server reads it. When in doubt, define your hooks on the server.

  • Both update and remove internally make use of find, so be aware that find/findOne hooks can fire for those methods.

  • find hooks are also fired when fetching documents for update, upsert and remove hooks.

  • If using the direct version to bypass a hook, any mongo operations done within nested callbacks of the direct operation will also by default run as direct. You can use the following line in a nested callback before the operation to unset the direct setting: CollectionHooks.directEnv = new Meteor.EnvironmentVariable(false)


Maintainers

Maintained by Meteor Community Packages and in particular by:

Contributors

More Repositories

1

meteor-autoform

AutoForm is a Meteor package that adds UI components and helpers to easily create basic forms with automatic insert and update events, and automatic reactive validation.
JavaScript
1,439
star
2

Meteor-CollectionFS

Reactive file manager for Meteor
JavaScript
1,051
star
3

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.
JavaScript
1,024
star
4

meteor-roles

Authorization package for Meteor, compatible with built-in accounts packages
JavaScript
920
star
5

meteor-simple-schema

Meteor integration package for simpl-schema
JavaScript
920
star
6

ground-db

GroundDB is a thin layer providing Meteor offline database and methods
JavaScript
569
star
7

meteor-user-status

Track user connection state and inactivity in Meteor.
JavaScript
557
star
8

meteor-publish-composite

Meteor.publishComposite provides a flexible way to publish a set of related documents from various collections using a reactive join
JavaScript
553
star
9

raix-push

DEPRECATED: Push notifications for cordova (ios, android) browser (Chrome, Safari, Firefox)
JavaScript
515
star
10

meteor-tabular

Reactive datatables for large or small datasets
JavaScript
363
star
11

meteor-autocomplete

Client/server autocompletion designed for Meteor's collections and reactivity.
CoffeeScript
350
star
12

meteor-scss

Node-sass wrapped to work with meteor.
JavaScript
311
star
13

meteor-partitioner

Transparently divide a single meteor app into several different instances shared between different groups of users.
CoffeeScript
152
star
14

meteor-timesync

NTP-style time synchronization between server and client, and facilities to use server time reactively in Meteor applications.
JavaScript
118
star
15

meteor-link-accounts

Meteor link account package. based on this PR https://github.com/meteor/meteor/pull/1133
JavaScript
116
star
16

mongo-collection-instances

πŸ—‚ Meteor package allowing Mongo Collection instance lookup by collection name
JavaScript
73
star
17

Meteor-EventEmitter

Client and server event emitter
JavaScript
72
star
18

meteor-postcss

PostCSS for Meteor
JavaScript
68
star
19

meteor-mocha

A Mocha test driver package for Meteor 1.3+. This package reports server AND client test results in the server console and can be used for running tests on a CI server or locally.
JavaScript
67
star
20

meteor-elastic-apm

Meteor Elastic APM integration
JavaScript
57
star
21

organization

Discussions on organization of the organization 🎩
41
star
22

meteor-schema-index

Control some MongoDB indexing with schema options
JavaScript
38
star
23

meteor-publication-collector

Test a Meteor publication by collecting its output.
JavaScript
33
star
24

meteor-collection-extensions

Safely and easily extend the (Meteor/Mongo).Collection constructor with custom functionality.
JavaScript
31
star
25

stratosphere

Meteor private package server
JavaScript
28
star
26

meteor-method-hooks

JavaScript
26
star
27

meteor-autoform-select2

Custom select2 input type for AutoForm
JavaScript
25
star
28

meteor-autoform-bs-datepicker

Custom "bootstrap-datepicker" input type for AutoForm
JavaScript
25
star
29

react-router-ssr

Simple isomorphic React SSR for Meteor with subscribed data re-hydration
JavaScript
24
star
30

denormalize

Simple denormalization for Meteor
JavaScript
20
star
31

meteor-autoform-bs-datetimepicker

Custom bootstrap-datetimepicker input type with timezone support for AutoForm
JavaScript
17
star
32

meteor-packages

Client for Meteor Package Server API
JavaScript
14
star
33

Packosphere

A Meteor package explorer for you, and if you are so inclined to help build it, by you.
TypeScript
13
star
34

meteor-schema-deny

Deny inserting or updating certain properties through schema options
JavaScript
12
star
35

check-npm-versions

Enforces "peer" npm dependencies in Meteor 1.3+ Atmosphere packages.
TypeScript
11
star
36

meteor-browser-tests

A helper package for Meteor test driver packages. Runs client tests in a headless browser.
JavaScript
11
star
37

template-package

Template package with CI and everything else to get started quickly with creating a new FOSS Meteor package.
JavaScript
10
star
38

meteor-stylus

A fork of meteor:stylus with mquandalle:stylus plugins
JavaScript
8
star
39

meteor-minifiers-autoprefix

JavaScript
6
star
40

meteor-autoform-bs-button-group-input

A Bootstrap button group theme for the "select-checkbox" and "select-radio" AutoForm input types
JavaScript
6
star
41

ground-minimax

Minimax is a thin layer for Meteor providing EJSON.minify and EJSON.maxify
JavaScript
6
star
42

meteor-typescript

Typescript compiler package
TypeScript
5
star
43

meteor-api-untethered

A collection of packages to make Meteor available to other environments.
JavaScript
5
star
44

awesome-meteor

Curated list of awesome Meteor.js things.
5
star
45

Meteor-EventState

DEPRECATED: Evented state
JavaScript
4
star
46

meteor-autoform-themes

Officially supported themes for aldeed:autoform
JavaScript
3
star
47

push

JavaScript
2
star