From 30546a8c7b67ce3c9b3c1710689baf13dd4fc666 Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 11 Sep 2018 22:23:45 +0200 Subject: [PATCH 1/9] ignore webstorm and meteornpm files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c48ce74..af22e09 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ node_modules npm-debug.log *.sublime-workspace .vscode +.idea +.npm \ No newline at end of file From abe9a0d5fb3714f143dea3b0f98111dff50eaea1 Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 11 Sep 2018 22:24:20 +0200 Subject: [PATCH 2/9] remove lodash close to everywhere --- find.js | 4 ++-- findone.js | 4 ++-- insert.js | 16 +++++++++------- package.js | 7 ++++++- remove.js | 24 +++++++++++++----------- update.js | 37 ++++++++++++++++++++----------------- upsert.js | 38 ++++++++++++++++++++------------------ 7 files changed, 72 insertions(+), 58 deletions(-) diff --git a/find.js b/find.js index 29c70ca..48d41f8 100644 --- a/find.js +++ b/find.js @@ -13,7 +13,7 @@ CollectionHooks.defineAdvice('find', function (userId, _super, instance, aspects // before if (!suppressAspects) { - _.each(aspects.before, function (o) { + aspects.before.forEach(function (o) { var r = o.aspect.call(ctx, userId, args[0], args[1]) if (r === false) abort = true }) @@ -23,7 +23,7 @@ CollectionHooks.defineAdvice('find', function (userId, _super, instance, aspects function after (cursor) { if (!suppressAspects) { - _.each(aspects.after, function (o) { + aspects.after.forEach(function (o) { o.aspect.call(ctx, userId, args[0], args[1], cursor) }) } diff --git a/findone.js b/findone.js index 9cac536..7598679 100644 --- a/findone.js +++ b/findone.js @@ -13,7 +13,7 @@ CollectionHooks.defineAdvice('findOne', function (userId, _super, instance, aspe // before if (!suppressAspects) { - _.each(aspects.before, function (o) { + aspects.before.forEach(function (o) { var r = o.aspect.call(ctx, userId, args[0], args[1]) if (r === false) abort = true }) @@ -23,7 +23,7 @@ CollectionHooks.defineAdvice('findOne', function (userId, _super, instance, aspe function after (doc) { if (!suppressAspects) { - _.each(aspects.after, function (o) { + aspects.after.forEach(function (o) { o.aspect.call(ctx, userId, args[0], args[1], doc) }) } diff --git a/insert.js b/insert.js index 67af13c..c5854ea 100644 --- a/insert.js +++ b/insert.js @@ -1,10 +1,12 @@ /* global CollectionHooks _ EJSON Mongo */ +var isFunction = require('lodash/isFunction') +var isObject = require('lodash/isObject') CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} - var callback = _.last(args) - var async = _.isFunction(callback) + var callback = args[args.length - 1] + var async = isFunction(callback) var abort, ret // args[0] : doc @@ -13,8 +15,8 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec // before if (!suppressAspects) { try { - _.each(aspects.before, function (o) { - var r = o.aspect.call(_.extend({transform: getTransform(args[0])}, ctx), userId, args[0]) + aspects.before.forEach(function (o) { + var r = o.aspect.call({transform: getTransform(args[0]), ...ctx}, userId, args[0]) if (r === false) abort = true }) @@ -30,7 +32,7 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec if (id) { // In some cases (namely Meteor.users on Meteor 1.4+), the _id property // is a raw mongo _id object. We need to extract the _id from this object - if (_.isObject(id) && id.ops) { + if (isObject(id) && id.ops) { // If _str then collection is using Mongo.ObjectID as ids if (doc._id._str) { id = new Mongo.ObjectID(doc._id._str.toString()) @@ -42,8 +44,8 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec doc._id = id } if (!suppressAspects) { - var lctx = _.extend({transform: getTransform(doc), _id: id, err: err}, ctx) - _.each(aspects.after, function (o) { + var lctx = {transform: getTransform(doc), _id: id, err: err, ...ctx} + aspects.after.forEach(function (o) { o.aspect.call(lctx, userId, doc) }) } diff --git a/package.js b/package.js index 9167e2d..7dd1a56 100644 --- a/package.js +++ b/package.js @@ -30,7 +30,8 @@ Package.onUse(function (api, where) { api.use([ 'underscore', 'ejson', - 'minimongo' + 'minimongo', + 'ecmascript' ]) api.use(['accounts-base'], ['client', 'server'], {weak: true}) @@ -121,3 +122,7 @@ Package.onTest(function (api) { // limited to only working on the server // api.addFiles('tests/fetch.js', 'server') }) + +Npm.depends({ + 'lodash': '4.17.10' +}); \ No newline at end of file diff --git a/remove.js b/remove.js index d2d413e..e4dc885 100644 --- a/remove.js +++ b/remove.js @@ -1,10 +1,12 @@ /* global CollectionHooks _ EJSON */ +var isFunction = require('lodash/isFunction') +var isEmpty = require('lodash/isEmpty') CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} - var callback = _.last(args) - var async = _.isFunction(callback) + var callback = args[args.length - 1] + var async = isFunction(callback) var docs var abort var prev = [] @@ -14,21 +16,21 @@ CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspec if (!suppressAspects) { try { - if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { + if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { docs = CollectionHooks.getDocs.call(self, instance, args[0]).fetch() } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspects.after)) { - _.each(docs, function (doc) { + if (!isEmpty(aspects.after)) { + docs.forEach(function (doc) { prev.push(EJSON.clone(doc)) }) } // before - _.each(aspects.before, function (o) { - _.each(docs, function (doc) { - var r = o.aspect.call(_.extend({transform: getTransform(doc)}, ctx), userId, doc) + aspects.before.forEach(function (o) { + docs.forEach(function (doc) { + var r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc) if (r === false) abort = true }) }) @@ -42,9 +44,9 @@ CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspec function after (err) { if (!suppressAspects) { - _.each(aspects.after, function (o) { - _.each(prev, function (doc) { - o.aspect.call(_.extend({transform: getTransform(doc), err: err}, ctx), userId, doc) + aspects.after.forEach(function (o) { + prev.forEach(function (doc) { + o.aspect.call({transform: getTransform(doc), err: err, ...ctx}, userId, doc) }) }) } diff --git a/update.js b/update.js index 222967e..ad833ad 100644 --- a/update.js +++ b/update.js @@ -1,10 +1,12 @@ /* global CollectionHooks _ EJSON */ +var isFunction = require('lodash/isFunction') +var isEmpty = require('lodash/isEmpty') CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} - var callback = _.last(args) - var async = _.isFunction(callback) + var callback = args[args.length - 1] + var async = isFunction(callback) var docs var docIds var fields @@ -16,38 +18,38 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec // args[2] : options (optional) // args[3] : callback - if (_.isFunction(args[2])) { + if (isFunction(args[2])) { callback = args[2] args[2] = {} } if (!suppressAspects) { try { - if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { + if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { fields = CollectionHooks.getFields(args[1]) docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() - docIds = _.map(docs, function (doc) { return doc._id }) + docIds = docs.map(function (doc) { return doc._id }) } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspects.after)) { + if (!isEmpty(aspects.after)) { prev.mutator = EJSON.clone(args[1]) prev.options = EJSON.clone(args[2]) if ( - _.some(aspects.after, function (o) { return o.options.fetchPrevious !== false }) && + aspects.after.some(function (o) { return o.options.fetchPrevious !== false }) && CollectionHooks.extendOptions(instance.hookOptions, {}, 'after', 'update').fetchPrevious !== false ) { prev.docs = {} - _.each(docs, function (doc) { + docs.forEach(function (doc) { prev.docs[doc._id] = EJSON.clone(doc) }) } } // before - _.each(aspects.before, function (o) { - _.each(docs, function (doc) { - var r = o.aspect.call(_.extend({transform: getTransform(doc)}, ctx), userId, doc, fields, args[1], args[2]) + aspects.before.forEach(function (o) { + docs.forEach(function (doc) { + var r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc, fields, args[1], args[2]) if (r === false) abort = true }) }) @@ -61,19 +63,20 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec function after (affected, err) { if (!suppressAspects) { - if (!_.isEmpty(aspects.after)) { + if (!isEmpty(aspects.after)) { var fields = CollectionHooks.getFields(args[1]) var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() } - _.each(aspects.after, function (o) { - _.each(docs, function (doc) { - o.aspect.call(_.extend({ + aspects.after.forEach(function (o) { + docs.forEach(function (doc) { + o.aspect.call({ transform: getTransform(doc), previous: prev.docs && prev.docs[doc._id], affected: affected, - err: err - }, ctx), userId, doc, fields, prev.mutator, prev.options) + err: err, + ...ctx + }, userId, doc, fields, prev.mutator, prev.options) }) }) } diff --git a/upsert.js b/upsert.js index 757d8be..2e5bf6b 100644 --- a/upsert.js +++ b/upsert.js @@ -1,10 +1,12 @@ /* global CollectionHooks _ EJSON */ +var isFunction = require('lodash/isFunction') +var isEmpty = require('lodash/isEmpty') CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspectGroup, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} - var callback = _.last(args) - var async = _.isFunction(callback) + var callback = args[args.length - 1] + var async = isFunction(callback) var docs var docIds var abort @@ -15,32 +17,32 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec // args[2] : options (optional) // args[3] : callback - if (_.isFunction(args[2])) { + if (isFunction(args[2])) { callback = args[2] args[2] = {} } if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.upsert.before)) { + if (!isEmpty(aspectGroup.upsert.before)) { docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() - docIds = _.map(docs, function (doc) { return doc._id }) + docIds = docs.map(function (doc) { return doc._id }) } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspectGroup.update.after)) { - if (_.some(aspectGroup.update.after, function (o) { return o.options.fetchPrevious !== false }) && + if (!isEmpty(aspectGroup.update.after)) { + if (aspectGroup.update.after.some(function (o) { return o.options.fetchPrevious !== false }) && CollectionHooks.extendOptions(instance.hookOptions, {}, 'after', 'update').fetchPrevious !== false) { prev.mutator = EJSON.clone(args[1]) prev.options = EJSON.clone(args[2]) prev.docs = {} - _.each(docs, function (doc) { + docs.forEach(function (doc) { prev.docs[doc._id] = EJSON.clone(doc) }) } } // before - _.each(aspectGroup.upsert.before, function (o) { + aspectGroup.upsert.before.forEach(function (o) { var r = o.aspect.call(ctx, userId, args[0], args[1], args[2]) if (r === false) abort = true }) @@ -50,19 +52,19 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec function afterUpdate (affected, err) { if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.update.after)) { + if (!isEmpty(aspectGroup.update.after)) { var fields = CollectionHooks.getFields(args[1]) var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() } - _.each(aspectGroup.update.after, function (o) { - _.each(docs, function (doc) { - o.aspect.call(_.extend({ + aspectGroup.update.after.forEach(function (o) { + docs.forEach(function (doc) { + o.aspect.call({ transform: getTransform(doc), previous: prev.docs && prev.docs[doc._id], affected: affected, - err: err - }, ctx), userId, doc, fields, prev.mutator, prev.options) + err: err, + ...ctx}, userId, doc, fields, prev.mutator, prev.options) }) }) } @@ -70,12 +72,12 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec function afterInsert (id, err) { if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.insert.after)) { + if (!isEmpty(aspectGroup.insert.after)) { var doc = CollectionHooks.getDocs.call(self, instance, {_id: id}, args[0], {}).fetch()[0] // 3rd argument passes empty object which causes magic logic to imply limit:1 - var lctx = _.extend({transform: getTransform(doc), _id: id, err: err}, ctx) + var lctx = {transform: getTransform(doc), _id: id, err: err, ...ctx} } - _.each(aspectGroup.insert.after, function (o) { + aspectGroup.insert.after.forEach(function (o) { o.aspect.call(lctx, userId, doc) }) } From 7d0d5826bad004e21ea0e6c8bdbde8d4114ca3e4 Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 11 Sep 2018 22:24:34 +0200 Subject: [PATCH 3/9] remove lodash in collection hooks --- collection-hooks.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/collection-hooks.js b/collection-hooks.js index f87726b..3dce992 100644 --- a/collection-hooks.js +++ b/collection-hooks.js @@ -5,6 +5,10 @@ // Aspect: User code that runs before/after (hook) // Advice: Wrapper code that knows when to call user code (aspects) // Pointcut: before/after +var isFunction = require('lodash/isFunction') +var toArray = require('lodash/toArray') +var omit = require('lodash/omit') + var advices = {} var Tracker = (Package.tracker && Package.tracker.Tracker) || Package.deps.Deps @@ -57,8 +61,8 @@ CollectionHooks.getUserId = function getUserId () { CollectionHooks.extendCollectionInstance = function extendCollectionInstance (self, constructor) { // Offer a public API to allow the user to define aspects // Example: collection.before.insert(func); - _.each(['before', 'after'], function (pointcut) { - _.each(advices, function (advice, method) { + ['before', 'after'].forEach(function (pointcut) { + Object.values(advices).forEach(function (advice, method) { if (advice === 'upsert' && pointcut === 'after') return Meteor._ensure(self, pointcut, method) @@ -92,7 +96,7 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se self.hookOptions = EJSON.clone(CollectionHooks.defaults) // Wrap mutator methods, letting the defined advice do the work - _.each(advices, function (advice, method) { + Object.values(advices).forEach(function (advice, method) { var collection = Meteor.isClient || method === 'upsert' ? self : self._collection // Store a reference to the original mutator method @@ -132,12 +136,12 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se } : self._hookAspects[method] || {}, function (doc) { return ( - _.isFunction(self._transform) + isFunction(self._transform) ? function (d) { return self._transform(d || doc) } : function (d) { return d || doc } ) }, - _.toArray(arguments), + toArray(arguments), false ) } @@ -157,11 +161,7 @@ CollectionHooks.initOptions = function initOptions (options, pointcut, method) { } CollectionHooks.extendOptions = function extendOptions (source, options, pointcut, method) { - options = _.extend(options || {}, source.all.all) - options = _.extend(options, source[pointcut].all) - options = _.extend(options, source.all[method]) - options = _.extend(options, source[pointcut][method]) - return options + return {...options, ...source.all.all, ...source[pointcut].all, ...source.all[method], ...source[pointcut][method]} } CollectionHooks.getDocs = function getDocs (collection, selector, options) { @@ -188,7 +188,7 @@ CollectionHooks.getDocs = function getDocs (collection, selector, options) { findOptions.limit = 1 } - _.extend(findOptions, _.omit(options, 'multi', 'upsert')) + findOptions = {...findOptions, ...omit(options, 'multi', 'upsert')} } // Unlike validators, we iterate over multiple docs, so use @@ -221,11 +221,11 @@ CollectionHooks.getFields = function getFields (mutator) { ] // ====ADDED END========================= - _.each(mutator, function (params, op) { + mutator.forEach(function (params, op) { // ====ADDED START======================= - if (_.contains(operators, op)) { + if (operators.includes(op)) { // ====ADDED END========================= - _.each(_.keys(params), function (field) { + Object.keys(params).forEach(function (field) { // treat dotted fields as if they are replacing their // top-level part if (field.indexOf('.') !== -1) { @@ -233,7 +233,7 @@ CollectionHooks.getFields = function getFields (mutator) { } // record the field we are trying to change - if (!_.contains(fields, field)) { + if (!fields.includes(field)) { fields.push(field) } }) From 1d52e91bf8daead08b79945758dbda1e289a5b91 Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 11 Sep 2018 22:55:31 +0200 Subject: [PATCH 4/9] fix object mappings --- collection-hooks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collection-hooks.js b/collection-hooks.js index 3dce992..717a167 100644 --- a/collection-hooks.js +++ b/collection-hooks.js @@ -62,7 +62,7 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se // Offer a public API to allow the user to define aspects // Example: collection.before.insert(func); ['before', 'after'].forEach(function (pointcut) { - Object.values(advices).forEach(function (advice, method) { + Object.entries(advices).forEach(function ([method, advice]) { if (advice === 'upsert' && pointcut === 'after') return Meteor._ensure(self, pointcut, method) @@ -96,7 +96,7 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se self.hookOptions = EJSON.clone(CollectionHooks.defaults) // Wrap mutator methods, letting the defined advice do the work - Object.values(advices).forEach(function (advice, method) { + Object.entries(advices).forEach(function ([method, advice]) { var collection = Meteor.isClient || method === 'upsert' ? self : self._collection // Store a reference to the original mutator method @@ -221,7 +221,7 @@ CollectionHooks.getFields = function getFields (mutator) { ] // ====ADDED END========================= - mutator.forEach(function (params, op) { + Object.entries(mutator).forEach(function ([op, params]) { // ====ADDED START======================= if (operators.includes(op)) { // ====ADDED END========================= From c44e377e1e45bd4dad4aad6c51c38c309e156210 Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 11 Sep 2018 22:58:18 +0200 Subject: [PATCH 5/9] remove underscore from bunle --- package.js | 1 - 1 file changed, 1 deletion(-) diff --git a/package.js b/package.js index 7dd1a56..822ee15 100644 --- a/package.js +++ b/package.js @@ -28,7 +28,6 @@ Package.onUse(function (api, where) { } api.use([ - 'underscore', 'ejson', 'minimongo', 'ecmascript' From ce2c4b9f80fa6f88cfe1ec8c589ba31034c65d1c Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Tue, 18 Sep 2018 15:45:15 +0200 Subject: [PATCH 6/9] manually require lodash in test --- package.js | 3 ++- tests/insecure_login.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.js b/package.js index 822ee15..82b73ca 100644 --- a/package.js +++ b/package.js @@ -67,7 +67,8 @@ Package.onTest(function (api) { 'accounts-base', 'accounts-password', 'tinytest', - 'test-helpers' + 'test-helpers', + "ecmascript" ]) api.addFiles('tests/insecure_login.js') diff --git a/tests/insecure_login.js b/tests/insecure_login.js index 3e9ad64..983c37c 100644 --- a/tests/insecure_login.js +++ b/tests/insecure_login.js @@ -1,3 +1,6 @@ +// hack taken from https://github.com/meteor/meteor/issues/9235#issuecomment-337257104 +// without the require lodash won't end up in the test bundle and the tests would fail +require('lodash/package.json') /* global Meteor Accounts InsecureLogin _ */ /* eslint-disable no-native-reassign, no-global-assign */ From 4eec20305deaba1ae1214553b6f85aa388e7fc8e Mon Sep 17 00:00:00 2001 From: lukas strassel Date: Wed, 19 Sep 2018 20:12:30 +0200 Subject: [PATCH 7/9] removev lodash again --- collection-hooks.js | 13 ++++--------- insert.js | 6 ++---- package.js | 9 +++------ remove.js | 9 +++------ tests/insecure_login.js | 3 --- update.js | 12 +++++------- upsert.js | 15 ++++++--------- 7 files changed, 23 insertions(+), 44 deletions(-) diff --git a/collection-hooks.js b/collection-hooks.js index 717a167..041a739 100644 --- a/collection-hooks.js +++ b/collection-hooks.js @@ -5,11 +5,6 @@ // Aspect: User code that runs before/after (hook) // Advice: Wrapper code that knows when to call user code (aspects) // Pointcut: before/after -var isFunction = require('lodash/isFunction') -var toArray = require('lodash/toArray') -var omit = require('lodash/omit') - - var advices = {} var Tracker = (Package.tracker && Package.tracker.Tracker) || Package.deps.Deps var publishUserId = Meteor.isServer && new Meteor.EnvironmentVariable() @@ -136,12 +131,12 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se } : self._hookAspects[method] || {}, function (doc) { return ( - isFunction(self._transform) + typeof self._transform === 'function' ? function (d) { return self._transform(d || doc) } : function (d) { return d || doc } ) }, - toArray(arguments), + Object.values(arguments), false ) } @@ -187,8 +182,8 @@ CollectionHooks.getDocs = function getDocs (collection, selector, options) { if (!options.multi) { findOptions.limit = 1 } - - findOptions = {...findOptions, ...omit(options, 'multi', 'upsert')} + const {multi, upsert, ...rest} = options + findOptions = {...findOptions, ...rest} } // Unlike validators, we iterate over multiple docs, so use diff --git a/insert.js b/insert.js index c5854ea..1e3a842 100644 --- a/insert.js +++ b/insert.js @@ -1,12 +1,10 @@ /* global CollectionHooks _ EJSON Mongo */ -var isFunction = require('lodash/isFunction') -var isObject = require('lodash/isObject') CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} var callback = args[args.length - 1] - var async = isFunction(callback) + var async = typeof callback === 'function' var abort, ret // args[0] : doc @@ -32,7 +30,7 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec if (id) { // In some cases (namely Meteor.users on Meteor 1.4+), the _id property // is a raw mongo _id object. We need to extract the _id from this object - if (isObject(id) && id.ops) { + if (typeof id === 'object' && id.ops) { // If _str then collection is using Mongo.ObjectID as ids if (doc._id._str) { id = new Mongo.ObjectID(doc._id._str.toString()) diff --git a/package.js b/package.js index 82b73ca..1676769 100644 --- a/package.js +++ b/package.js @@ -30,7 +30,8 @@ Package.onUse(function (api, where) { api.use([ 'ejson', 'minimongo', - 'ecmascript' + 'ecmascript', + 'underscore' ]) api.use(['accounts-base'], ['client', 'server'], {weak: true}) @@ -121,8 +122,4 @@ Package.onTest(function (api) { // NOTE: fetch can only work server-side because find's 'fields' option is // limited to only working on the server // api.addFiles('tests/fetch.js', 'server') -}) - -Npm.depends({ - 'lodash': '4.17.10' -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/remove.js b/remove.js index e4dc885..e38381c 100644 --- a/remove.js +++ b/remove.js @@ -1,12 +1,9 @@ /* global CollectionHooks _ EJSON */ -var isFunction = require('lodash/isFunction') -var isEmpty = require('lodash/isEmpty') - CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} var callback = args[args.length - 1] - var async = isFunction(callback) + var async = typeof callback === 'function' var docs var abort var prev = [] @@ -16,12 +13,12 @@ CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspec if (!suppressAspects) { try { - if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { + if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { docs = CollectionHooks.getDocs.call(self, instance, args[0]).fetch() } // copy originals for convenience for the 'after' pointcut - if (!isEmpty(aspects.after)) { + if (!_.isEmpty(aspects.after)) { docs.forEach(function (doc) { prev.push(EJSON.clone(doc)) }) diff --git a/tests/insecure_login.js b/tests/insecure_login.js index 983c37c..3e9ad64 100644 --- a/tests/insecure_login.js +++ b/tests/insecure_login.js @@ -1,6 +1,3 @@ -// hack taken from https://github.com/meteor/meteor/issues/9235#issuecomment-337257104 -// without the require lodash won't end up in the test bundle and the tests would fail -require('lodash/package.json') /* global Meteor Accounts InsecureLogin _ */ /* eslint-disable no-native-reassign, no-global-assign */ diff --git a/update.js b/update.js index ad833ad..3094ed1 100644 --- a/update.js +++ b/update.js @@ -1,12 +1,10 @@ /* global CollectionHooks _ EJSON */ -var isFunction = require('lodash/isFunction') -var isEmpty = require('lodash/isEmpty') CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} var callback = args[args.length - 1] - var async = isFunction(callback) + var async = typeof callback === 'function' var docs var docIds var fields @@ -18,21 +16,21 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec // args[2] : options (optional) // args[3] : callback - if (isFunction(args[2])) { + if (typeof args[2] === 'function') { callback = args[2] args[2] = {} } if (!suppressAspects) { try { - if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { + if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { fields = CollectionHooks.getFields(args[1]) docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() docIds = docs.map(function (doc) { return doc._id }) } // copy originals for convenience for the 'after' pointcut - if (!isEmpty(aspects.after)) { + if (!_.isEmpty(aspects.after)) { prev.mutator = EJSON.clone(args[1]) prev.options = EJSON.clone(args[2]) if ( @@ -63,7 +61,7 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec function after (affected, err) { if (!suppressAspects) { - if (!isEmpty(aspects.after)) { + if (!_.isEmpty(aspects.after)) { var fields = CollectionHooks.getFields(args[1]) var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() } diff --git a/upsert.js b/upsert.js index 2e5bf6b..d6667ae 100644 --- a/upsert.js +++ b/upsert.js @@ -1,12 +1,9 @@ /* global CollectionHooks _ EJSON */ -var isFunction = require('lodash/isFunction') -var isEmpty = require('lodash/isEmpty') - CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspectGroup, getTransform, args, suppressAspects) { var self = this var ctx = {context: self, _super: _super, args: args} var callback = args[args.length - 1] - var async = isFunction(callback) + var async = typeof callback === 'function' var docs var docIds var abort @@ -17,19 +14,19 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec // args[2] : options (optional) // args[3] : callback - if (isFunction(args[2])) { + if (typeof args[2] === 'function') { callback = args[2] args[2] = {} } if (!suppressAspects) { - if (!isEmpty(aspectGroup.upsert.before)) { + if (!_.isEmpty(aspectGroup.upsert.before)) { docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() docIds = docs.map(function (doc) { return doc._id }) } // copy originals for convenience for the 'after' pointcut - if (!isEmpty(aspectGroup.update.after)) { + if (!_.isEmpty(aspectGroup.update.after)) { if (aspectGroup.update.after.some(function (o) { return o.options.fetchPrevious !== false }) && CollectionHooks.extendOptions(instance.hookOptions, {}, 'after', 'update').fetchPrevious !== false) { prev.mutator = EJSON.clone(args[1]) @@ -52,7 +49,7 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec function afterUpdate (affected, err) { if (!suppressAspects) { - if (!isEmpty(aspectGroup.update.after)) { + if (!_.isEmpty(aspectGroup.update.after)) { var fields = CollectionHooks.getFields(args[1]) var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() } @@ -72,7 +69,7 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec function afterInsert (id, err) { if (!suppressAspects) { - if (!isEmpty(aspectGroup.insert.after)) { + if (!_.isEmpty(aspectGroup.insert.after)) { var doc = CollectionHooks.getDocs.call(self, instance, {_id: id}, args[0], {}).fetch()[0] // 3rd argument passes empty object which causes magic logic to imply limit:1 var lctx = {transform: getTransform(doc), _id: id, err: err, ...ctx} } From 6636180b853741ff841dbcf7b090fc30d18b51a6 Mon Sep 17 00:00:00 2001 From: sebakerckhof Date: Fri, 28 Sep 2018 10:52:18 +0200 Subject: [PATCH 8/9] Modernize --- advices.js | 9 ++ client.js | 23 +++ collection-hooks.js | 133 ++++++----------- find.js | 29 ++-- findone.js | 26 ++-- insert.js | 39 +++-- package.js | 100 ++----------- remove.js | 54 ++++--- server.js | 43 ++++++ tests/collectionfs.js | 102 ------------- tests/direct.js | 156 -------------------- tests/find_users.js | 71 --------- tests/insecure_login.js | 58 -------- tst/client/insecure_login.js | 15 ++ tst/client/main.js | 2 + tst/common.js | 23 +++ {tests => tst}/compat.js | 26 ++-- tst/direct.js | 160 +++++++++++++++++++++ {tests => tst}/find.js | 14 +- {tests => tst}/find_findone_userid.js | 89 ++++++------ tst/find_users.js | 71 +++++++++ {tests => tst}/findone.js | 14 +- {tests => tst}/hooks_in_loop.js | 19 +-- tst/insecure_login.js | 18 +++ {tests => tst}/insert_allow.js | 15 +- {tests => tst}/insert_both.js | 19 +-- {tests => tst}/insert_local.js | 13 +- {tests => tst}/meteor_1_4_id_object.js | 21 +-- {tests => tst}/multiple_hooks.js | 10 +- {tests => tst}/optional_previous.js | 18 +-- {tests => tst}/remove_allow.js | 15 +- {tests => tst}/remove_both.js | 23 +-- {tests => tst}/remove_local.js | 19 +-- {tests => tst/server}/fetch.js | 13 +- tst/server/insecure_login.js | 28 ++++ {tests => tst/server}/insert_user.js | 11 +- tst/server/main.js | 12 ++ {tests => tst/server}/update_user.js | 12 +- {tests => tst/server}/update_without_id.js | 18 +-- {tests => tst}/transform.js | 32 ++--- {tests => tst}/trycatch.js | 18 +-- {tests => tst}/update_allow.js | 15 +- {tests => tst}/update_both.js | 30 ++-- {tests => tst}/update_local.js | 25 ++-- {tests => tst}/upsert.js | 21 +-- update.js | 81 +++++------ upsert.js | 98 ++++++------- users-compat.js | 7 +- 48 files changed, 861 insertions(+), 1007 deletions(-) create mode 100644 advices.js create mode 100644 client.js create mode 100644 server.js delete mode 100644 tests/collectionfs.js delete mode 100644 tests/direct.js delete mode 100644 tests/find_users.js delete mode 100644 tests/insecure_login.js create mode 100644 tst/client/insecure_login.js create mode 100644 tst/client/main.js create mode 100644 tst/common.js rename {tests => tst}/compat.js (77%) create mode 100644 tst/direct.js rename {tests => tst}/find.js (80%) rename {tests => tst}/find_findone_userid.js (71%) create mode 100644 tst/find_users.js rename {tests => tst}/findone.js (80%) rename {tests => tst}/hooks_in_loop.js (82%) create mode 100644 tst/insecure_login.js rename {tests => tst}/insert_allow.js (76%) rename {tests => tst}/insert_both.js (85%) rename {tests => tst}/insert_local.js (83%) rename {tests => tst}/meteor_1_4_id_object.js (68%) rename {tests => tst}/multiple_hooks.js (87%) rename {tests => tst}/optional_previous.js (89%) rename {tests => tst}/remove_allow.js (77%) rename {tests => tst}/remove_both.js (89%) rename {tests => tst}/remove_local.js (84%) rename {tests => tst/server}/fetch.js (60%) create mode 100644 tst/server/insecure_login.js rename {tests => tst/server}/insert_user.js (68%) create mode 100644 tst/server/main.js rename {tests => tst/server}/update_user.js (73%) rename {tests => tst/server}/update_without_id.js (79%) rename {tests => tst}/transform.js (51%) rename {tests => tst}/trycatch.js (77%) rename {tests => tst}/update_allow.js (79%) rename {tests => tst}/update_both.js (78%) rename {tests => tst}/update_local.js (85%) rename {tests => tst}/upsert.js (90%) diff --git a/advices.js b/advices.js new file mode 100644 index 0000000..3c21638 --- /dev/null +++ b/advices.js @@ -0,0 +1,9 @@ +import './insert.js' +import './update.js' +import './remove.js' +import './upsert.js' +import './find.js' +import './findone.js' + +// Load after all advices have been defined +import './users-compat.js' \ No newline at end of file diff --git a/client.js b/client.js new file mode 100644 index 0000000..38b46fa --- /dev/null +++ b/client.js @@ -0,0 +1,23 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; +import { CollectionHooks} from './collection-hooks.js' + +CollectionHooks.getUserId = function getUserId () { + let userId + + Tracker.nonreactive(() => { + userId = Meteor.userId && Meteor.userId() + }) + + if (userId == null) { + userId = CollectionHooks.defaultUserId + } + + return userId +} + +import './advices'; + +export { + CollectionHooks +}; \ No newline at end of file diff --git a/collection-hooks.js b/collection-hooks.js index 041a739..056d73a 100644 --- a/collection-hooks.js +++ b/collection-hooks.js @@ -1,3 +1,8 @@ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { EJSON } from 'meteor/ejson'; +import { LocalCollection } from 'meteor/minimongo'; + /* global Package Meteor Mongo LocalCollection CollectionHooks _ EJSON */ /* eslint-disable no-proto, no-native-reassign, no-global-assign */ @@ -5,54 +10,23 @@ // Aspect: User code that runs before/after (hook) // Advice: Wrapper code that knows when to call user code (aspects) // Pointcut: before/after -var advices = {} -var Tracker = (Package.tracker && Package.tracker.Tracker) || Package.deps.Deps -var publishUserId = Meteor.isServer && new Meteor.EnvironmentVariable() +const advices = {} -CollectionHooks = { +export const CollectionHooks = { defaults: { before: {insert: {}, update: {}, remove: {}, upsert: {}, find: {}, findOne: {}, all: {}}, after: {insert: {}, update: {}, remove: {}, find: {}, findOne: {}, all: {}}, all: {insert: {}, update: {}, remove: {}, find: {}, findOne: {}, all: {}} }, directEnv: new Meteor.EnvironmentVariable(), - directOp: function directOp (func) { + directOp(func) { return this.directEnv.withValue(true, func) }, - hookedOp: function hookedOp (func) { + hookedOp(func) { return this.directEnv.withValue(false, func) } } -CollectionHooks.getUserId = function getUserId () { - var userId - - if (Meteor.isClient) { - Tracker.nonreactive(function () { - userId = Meteor.userId && Meteor.userId() - }) - } - - if (Meteor.isServer) { - try { - // Will throw an error unless within method call. - // Attempt to recover gracefully by catching: - userId = Meteor.userId && Meteor.userId() - } catch (e) {} - - if (userId == null) { - // Get the userId if we are in a publish function. - userId = publishUserId.get() - } - } - - if (userId == null) { - userId = CollectionHooks.defaultUserId - } - - return userId -} - CollectionHooks.extendCollectionInstance = function extendCollectionInstance (self, constructor) { // Offer a public API to allow the user to define aspects // Example: collection.before.insert(func); @@ -65,19 +39,19 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se self._hookAspects[method][pointcut] = [] self[pointcut][method] = function (aspect, options) { - var len = self._hookAspects[method][pointcut].push({ - aspect: aspect, + const len = self._hookAspects[method][pointcut].push({ + aspect, options: CollectionHooks.initOptions(options, pointcut, method) }) return { - replace: function (aspect, options) { + replace(aspect, options) { self._hookAspects[method][pointcut].splice(len - 1, 1, { aspect: aspect, options: CollectionHooks.initOptions(options, pointcut, method) }) }, - remove: function () { + remove() { self._hookAspects[method][pointcut].splice(len - 1, 1) } } @@ -92,22 +66,21 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se // Wrap mutator methods, letting the defined advice do the work Object.entries(advices).forEach(function ([method, advice]) { - var collection = Meteor.isClient || method === 'upsert' ? self : self._collection + const collection = Meteor.isClient || method === 'upsert' ? self : self._collection // Store a reference to the original mutator method - var _super = collection[method] + const _super = collection[method] Meteor._ensure(self, 'direct', method) - self.direct[method] = function () { - var args = arguments + self.direct[method] = function (...args) { return CollectionHooks.directOp(function () { return constructor.prototype[method].apply(self, args) }) } - collection[method] = function () { + collection[method] = function (...args) { if (CollectionHooks.directEnv.get() === true) { - return _super.apply(collection, arguments) + return _super.apply(collection, args) } // NOTE: should we decide to force `update` with `{upsert:true}` to use @@ -115,7 +88,7 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se // realize that Meteor won't distinguish between an `update` and an // `insert` though, so we'll end up with `after.update` getting called // even on an `insert`. That's why we've chosen to disable this for now. - // if (method === "update" && _.isObject(arguments[2]) && arguments[2].upsert) { + // if (method === "update" && Object(args[2]) === args[2] && args[2].upsert) { // method = "upsert"; // advice = CollectionHooks.getAdvice(method); // } @@ -136,37 +109,33 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance (se : function (d) { return d || doc } ) }, - Object.values(arguments), + args, false ) } }) } -CollectionHooks.defineAdvice = function defineAdvice (method, advice) { +CollectionHooks.defineAdvice = (method, advice) => { advices[method] = advice } -CollectionHooks.getAdvice = function getAdvice (method) { - return advices[method] -} +CollectionHooks.getAdvice = method => advices[method]; -CollectionHooks.initOptions = function initOptions (options, pointcut, method) { - return CollectionHooks.extendOptions(CollectionHooks.defaults, options, pointcut, method) -} +CollectionHooks.initOptions = (options, pointcut, method) => + CollectionHooks.extendOptions(CollectionHooks.defaults, options, pointcut, method); -CollectionHooks.extendOptions = function extendOptions (source, options, pointcut, method) { - return {...options, ...source.all.all, ...source[pointcut].all, ...source.all[method], ...source[pointcut][method]} -} +CollectionHooks.extendOptions = (source, options, pointcut, method) => + ({...options, ...source.all.all, ...source[pointcut].all, ...source.all[method], ...source[pointcut][method]}); CollectionHooks.getDocs = function getDocs (collection, selector, options) { - var findOptions = {transform: null, reactive: false} // added reactive: false + const findOptions = {transform: null, reactive: false} // added reactive: false /* // No "fetch" support at this time. if (!this._validators.fetchAllFields) { findOptions.fields = {}; - _.each(this._validators.fetch, function(fieldName) { + this._validators.fetch.forEach(function(fieldName) { findOptions.fields[fieldName] = 1; }); } @@ -182,8 +151,8 @@ CollectionHooks.getDocs = function getDocs (collection, selector, options) { if (!options.multi) { findOptions.limit = 1 } - const {multi, upsert, ...rest} = options - findOptions = {...findOptions, ...rest} + const { multi, upsert, ...rest } = options + Object.assign(findOptions, rest); } // Unlike validators, we iterate over multiple docs, so use @@ -197,9 +166,9 @@ CollectionHooks.getDocs = function getDocs (collection, selector, options) { // case this code changes. CollectionHooks.getFields = function getFields (mutator) { // compute modified fields - var fields = [] + const fields = [] // ====ADDED START======================= - var operators = [ + const operators = [ '$addToSet', '$bit', '$currentDate', @@ -243,9 +212,8 @@ CollectionHooks.getFields = function getFields (mutator) { } CollectionHooks.reassignPrototype = function reassignPrototype (instance, constr) { - var hasSetPrototypeOf = typeof Object.setPrototypeOf === 'function' - - if (!constr) constr = typeof Mongo !== 'undefined' ? Mongo.Collection : Meteor.Collection + const hasSetPrototypeOf = typeof Object.setPrototypeOf === 'function' + constr = constr || Mongo.Collection // __proto__ is not available in < IE11 // Note: Assigning a prototype dynamically has performance implications @@ -260,11 +228,11 @@ CollectionHooks.wrapCollection = function wrapCollection (ns, as) { if (!as._CollectionConstructor) as._CollectionConstructor = as.Collection if (!as._CollectionPrototype) as._CollectionPrototype = new as.Collection(null) - var constructor = as._CollectionConstructor - var proto = as._CollectionPrototype + const constructor = as._CollectionConstructor + const proto = as._CollectionPrototype - ns.Collection = function () { - var ret = constructor.apply(this, arguments) + ns.Collection = function (...args) { + const ret = constructor.apply(this, args) CollectionHooks.extendCollectionInstance(this, constructor) return ret } @@ -272,10 +240,8 @@ CollectionHooks.wrapCollection = function wrapCollection (ns, as) { ns.Collection.prototype = proto ns.Collection.prototype.constructor = ns.Collection - for (var prop in constructor) { - if (constructor.hasOwnProperty(prop)) { - ns.Collection[prop] = constructor[prop] - } + for (let prop of Object.keys(constructor)) { + ns.Collection[prop] = constructor[prop] } } @@ -288,22 +254,3 @@ if (typeof Mongo !== 'undefined') { CollectionHooks.wrapCollection(Meteor, Meteor) } -if (Meteor.isServer) { - var _publish = Meteor.publish - Meteor.publish = function (name, handler, options) { - return _publish.call(this, name, function () { - // This function is called repeatedly in publications - var ctx = this - var args = arguments - return publishUserId.withValue(ctx && ctx.userId, function () { - return handler.apply(ctx, args) - }) - }, options) - } - - // Make the above available for packages with hooks that want to determine - // whether they are running inside a publish function or not. - CollectionHooks.isWithinPublish = function isWithinPublish () { - return publishUserId.get() !== undefined - } -} diff --git a/find.js b/find.js index 48d41f8..97200cc 100644 --- a/find.js +++ b/find.js @@ -1,35 +1,32 @@ -/* global CollectionHooks _ */ +import { CollectionHooks } from './collection-hooks'; -CollectionHooks.defineAdvice('find', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var ret, abort - - // args[0] : selector - // args[1] : options - args[0] = instance._getFindSelector(args) - args[1] = instance._getFindOptions(args) +CollectionHooks.defineAdvice('find', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { + const ctx = {context: this, _super, args} + const selector = instance._getFindSelector(args) + const options = instance._getFindOptions(args) + let ret + let abort // before if (!suppressAspects) { - aspects.before.forEach(function (o) { - var r = o.aspect.call(ctx, userId, args[0], args[1]) + aspects.before.forEach((o) => { + const r = o.aspect.call(ctx, userId, selector, options) if (r === false) abort = true }) if (abort) return instance.find(undefined) } - function after (cursor) { + const after = (cursor) => { if (!suppressAspects) { - aspects.after.forEach(function (o) { - o.aspect.call(ctx, userId, args[0], args[1], cursor) + aspects.after.forEach((o) => { + o.aspect.call(ctx, userId, selector, options, cursor) }) } } - ret = _super.apply(self, args) + ret = _super.call(this, selector, options) after(ret) return ret diff --git a/findone.js b/findone.js index 7598679..ea2000f 100644 --- a/findone.js +++ b/findone.js @@ -1,20 +1,16 @@ -/* global CollectionHooks _ */ +import { CollectionHooks } from './collection-hooks'; CollectionHooks.defineAdvice('findOne', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var ret, abort - - // args[0] : selector - // args[1] : options - - args[0] = instance._getFindSelector(args) - args[1] = instance._getFindOptions(args) + const ctx = {context: this, _super, args}; + const selector = instance._getFindSelector(args) + const options = instance._getFindOptions(args) + let ret + let abort // before if (!suppressAspects) { - aspects.before.forEach(function (o) { - var r = o.aspect.call(ctx, userId, args[0], args[1]) + aspects.before.forEach((o) => { + const r = o.aspect.call(ctx, userId, selector, options) if (r === false) abort = true }) @@ -23,13 +19,13 @@ CollectionHooks.defineAdvice('findOne', function (userId, _super, instance, aspe function after (doc) { if (!suppressAspects) { - aspects.after.forEach(function (o) { - o.aspect.call(ctx, userId, args[0], args[1], doc) + aspects.after.forEach((o) => { + o.aspect.call(ctx, userId, selector, options, doc) }) } } - ret = _super.apply(self, args) + ret = _super.call(this, selector, options) after(ret) return ret diff --git a/insert.js b/insert.js index 1e3a842..1105579 100644 --- a/insert.js +++ b/insert.js @@ -1,32 +1,29 @@ -/* global CollectionHooks _ EJSON Mongo */ +import { EJSON } from 'meteor/ejson'; +import { CollectionHooks } from './collection-hooks'; CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var callback = args[args.length - 1] - var async = typeof callback === 'function' - var abort, ret - - // args[0] : doc - // args[1] : callback + const ctx = {context: this, _super, args} + let [doc, callback] = args; + const async = typeof callback === 'function' + let abort + let ret // before if (!suppressAspects) { try { - aspects.before.forEach(function (o) { - var r = o.aspect.call({transform: getTransform(args[0]), ...ctx}, userId, args[0]) + aspects.before.forEach((o) => { + const r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc) if (r === false) abort = true }) if (abort) return } catch (e) { - if (async) return callback.call(self, e) + if (async) return callback.call(this, e) throw e } } - function after (id, err) { - var doc = args[0] + const after = (id, err) => { if (id) { // In some cases (namely Meteor.users on Meteor 1.4+), the _id property // is a raw mongo _id object. We need to extract the _id from this object @@ -38,12 +35,12 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec id = id.ops && id.ops[0] && id.ops[0]._id } } - doc = EJSON.clone(args[0]) + doc = EJSON.clone(doc) doc._id = id } if (!suppressAspects) { - var lctx = {transform: getTransform(doc), _id: id, err: err, ...ctx} - aspects.after.forEach(function (o) { + const lctx = {transform: getTransform(doc), _id: id, err, ...ctx} + aspects.after.forEach((o) => { o.aspect.call(lctx, userId, doc) }) } @@ -51,13 +48,13 @@ CollectionHooks.defineAdvice('insert', function (userId, _super, instance, aspec } if (async) { - args[args.length - 1] = function (err, obj) { + const wrappedCallback = function (err, obj, ...args) { after((obj && obj[0] && obj[0]._id) || obj, err) - return callback.apply(this, arguments) + return callback.call(this, err, obj, ...args) } - return _super.apply(self, args) + return _super.call(this, doc, wrappedCallback) } else { - ret = _super.apply(self, args) + ret = _super.call(this, doc, callback) return after((ret && ret[0] && ret[0]._id) || ret) } }) diff --git a/package.js b/package.js index 1676769..12e2837 100644 --- a/package.js +++ b/package.js @@ -3,51 +3,26 @@ Package.describe({ name: 'matb33:collection-hooks', summary: 'Extends Mongo.Collection with before/after hooks for insert/update/remove/find/findOne', - version: '0.9.0-rc.4', + version: '0.10.0', git: 'https://github.com/matb33/meteor-collection-hooks.git' }) -Package.onUse = Package.onUse || Package.on_use // backwards-compat -Package.onTest = Package.onTest || Package.on_test // backwards-compat - Package.onUse(function (api, where) { - api.addFiles = api.addFiles || api.add_files // backwards-compat - - if (api.versionsFrom) { // 0.9.0+ litmus test - api.versionsFrom('1.3.5.1') - api.use([ - 'mongo', - 'tracker' - ]) - } else { - api.use([ - 'mongo-livedata', - 'deps' - ]) - } + api.versionsFrom('1.7.0.5') api.use([ + 'mongo', + 'tracker', 'ejson', 'minimongo', 'ecmascript', - 'underscore' ]) api.use(['accounts-base'], ['client', 'server'], {weak: true}) - api.addFiles([ - 'collection-hooks.js', - 'insert.js', - 'update.js', - 'remove.js', - 'upsert.js', - 'find.js', - 'findone.js' - ]) - - // Load after all advices have been defined - api.addFiles('users-compat.js') + api.mainModule('client.js', 'client'); + api.mainModule('server.js', 'server'); api.export('CollectionHooks') }) @@ -55,71 +30,18 @@ Package.onUse(function (api, where) { Package.onTest(function (api) { // var isTravisCI = process && process.env && process.env.TRAVIS - api.addFiles = api.addFiles || api.add_files // backwards-compat - - if (api.versionsFrom) { // 0.9.0+ litmus test - api.versionsFrom('1.3.5.1') - api.use('mongo') - } + api.versionsFrom('1.7.0.5') api.use([ 'matb33:collection-hooks', - 'underscore', 'accounts-base', 'accounts-password', + 'mongo', 'tinytest', 'test-helpers', "ecmascript" - ]) - - api.addFiles('tests/insecure_login.js') - - // local = minimongo (on server and client) - // both = minimongo on client and server, mongo on server, with mutator methods - // allow = same as both but with an allow rule test - api.addFiles('tests/insert_local.js') - api.addFiles('tests/insert_both.js') - api.addFiles('tests/insert_allow.js') - api.addFiles('tests/insert_user.js', 'server') - - api.addFiles('tests/update_local.js') - api.addFiles('tests/update_both.js') - api.addFiles('tests/update_allow.js') - api.addFiles('tests/update_user.js', 'server') - api.addFiles('tests/update_without_id.js', 'server') - - api.addFiles('tests/remove_local.js') - api.addFiles('tests/remove_both.js') - api.addFiles('tests/remove_allow.js') - - api.addFiles('tests/find.js') - api.addFiles('tests/findone.js') - api.addFiles('tests/find_users.js') - api.addFiles('tests/find_findone_userid.js') - - api.addFiles('tests/multiple_hooks.js') - api.addFiles('tests/transform.js') - api.addFiles('tests/direct.js') - api.addFiles('tests/optional_previous.js') - api.addFiles('tests/compat.js') - api.addFiles('tests/hooks_in_loop.js') - api.addFiles('tests/upsert.js') - api.addFiles('tests/trycatch.js') - api.addFiles('tests/meteor_1_4_id_object.js') - - // NOTE: not testing against CollectionFS anymore, getting weird warning: - // https://github.com/CollectionFS/Meteor-CollectionFS/issues/688 - // if (!isTravisCI) { - // api.use([ - // 'cfs:standard-packages', - // 'cfs:filesystem' - // ]) - - // api.addFiles('tests/collectionfs.js') - // } + ]); - // NOTE: not supporting fetch for the time being. - // NOTE: fetch can only work server-side because find's 'fields' option is - // limited to only working on the server - // api.addFiles('tests/fetch.js', 'server') + api.mainModule('tst/client/main.js', 'client'); + api.mainModule('tst/server/main.js', 'server'); }) \ No newline at end of file diff --git a/remove.js b/remove.js index e38381c..fcdb9a9 100644 --- a/remove.js +++ b/remove.js @@ -1,62 +1,60 @@ -/* global CollectionHooks _ EJSON */ -CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var callback = args[args.length - 1] - var async = typeof callback === 'function' - var docs - var abort - var prev = [] +import { EJSON } from 'meteor/ejson'; +import { CollectionHooks } from './collection-hooks'; + +const isEmpty = a => !Array.isArray(a) || !a.length; - // args[0] : selector - // args[1] : callback +CollectionHooks.defineAdvice('remove', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { + const ctx = {context: this, _super, args}; + const [ selector, callback ] = args; + const async = typeof callback === 'function' + let docs + let abort + let prev = [] if (!suppressAspects) { try { - if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { - docs = CollectionHooks.getDocs.call(self, instance, args[0]).fetch() + if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { + docs = CollectionHooks.getDocs.call(this, instance, selector).fetch() } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspects.after)) { - docs.forEach(function (doc) { - prev.push(EJSON.clone(doc)) - }) + if (!isEmpty(aspects.after)) { + docs.forEach(doc => prev.push(EJSON.clone(doc))) } // before - aspects.before.forEach(function (o) { - docs.forEach(function (doc) { - var r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc) + aspects.before.forEach((o) => { + docs.forEach((doc) => { + const r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc) if (r === false) abort = true }) }) if (abort) return 0 } catch (e) { - if (async) return callback.call(self, e) + if (async) return callback.call(this, e) throw e } } function after (err) { if (!suppressAspects) { - aspects.after.forEach(function (o) { - prev.forEach(function (doc) { - o.aspect.call({transform: getTransform(doc), err: err, ...ctx}, userId, doc) + aspects.after.forEach((o) => { + prev.forEach((doc) => { + o.aspect.call({transform: getTransform(doc), err, ...ctx}, userId, doc) }) }) } } if (async) { - args[args.length - 1] = function (err) { + const wrappedCallback = function (err, ...args) { after(err) - return callback.apply(this, arguments) + return callback.call(this, err, ...args) } - return _super.apply(self, args) + return _super.call(this, selector, wrappedCallback) } else { - var result = _super.apply(self, args) + const result = _super.call(this, selector, callback) after() return result } diff --git a/server.js b/server.js new file mode 100644 index 0000000..19fa5e1 --- /dev/null +++ b/server.js @@ -0,0 +1,43 @@ +import { Meteor } from 'meteor/meteor'; +import { CollectionHooks } from './collection-hooks'; + +const publishUserId = new Meteor.EnvironmentVariable() + +CollectionHooks.getUserId = function getUserId () { + let userId + + try { + // Will throw an error unless within method call. + // Attempt to recover gracefully by catching: + userId = Meteor.userId && Meteor.userId() + } catch (e) {} + + if (userId == null) { + // Get the userId if we are in a publish function. + userId = publishUserId.get() + } + + if (userId == null) { + userId = CollectionHooks.defaultUserId + } + + return userId +} + +const _publish = Meteor.publish +Meteor.publish = function (name, handler, options) { + return _publish.call(this, name, function (...args) { + // This function is called repeatedly in publications + return publishUserId.withValue(this && this.userId, () => handler.apply(this, args)); + }, options) +} + +// Make the above available for packages with hooks that want to determine +// whether they are running inside a publish function or not. +CollectionHooks.isWithinPublish = () => publishUserId.get() !== void 0; + +import './advices'; + +export { + CollectionHooks +}; \ No newline at end of file diff --git a/tests/collectionfs.js b/tests/collectionfs.js deleted file mode 100644 index 9806f29..0000000 --- a/tests/collectionfs.js +++ /dev/null @@ -1,102 +0,0 @@ -/* global Tinytest Meteor InsecureLogin FS */ - -var fsCollection = new FS.Collection('testfiles', { - stores: [ - new FS.Store.FileSystem('testfiles', {path: '/tmp'}) - ] -}) - -var dataUri = 'data:image/gifbase64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7' - -if (Meteor.isServer) { - fsCollection.files.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } - }) - - Meteor.publish('testfilespublish', function () { - return fsCollection.find() - }) -} else { - Meteor.subscribe('testfilespublish') -} - -var counts = {} - -function reset () { - counts = { - before: { - insert: 0, - update: 0, - remove: 0 - }, - after: { - insert: 0, - update: 0, - remove: 0 - } - } -} - -fsCollection.files.before.insert(function () { counts.before.insert++ }) -fsCollection.files.before.update(function () { counts.before.update++ }) -fsCollection.files.before.remove(function () { counts.before.remove++ }) - -fsCollection.files.after.insert(function () { counts.after.insert++ }) -fsCollection.files.after.update(function () { counts.after.update++ }) -fsCollection.files.after.remove(function () { counts.after.remove++ }) - -Tinytest.addAsync('CollectionFS - ensure insert, update, remove hooks work properly', function (test, next) { - function _insert (callback) { - var testFile = new FS.File(dataUri) - fsCollection.insert(testFile, function (err, fileObj) { - if (err) throw err - testFile.name('collectionfs-test-' + (Meteor.isServer ? 'SERVER' : 'CLIENT') + '.gif') - - // TODO: find a better way to wait for the file to be written - Meteor.setTimeout(function () { - callback(err, fileObj) - }, 100) - }) - } - - function _update (id, callback) { - fsCollection.files.update(id, {$set: {'metadata.test': 1}}, function (err) { - if (err) throw err - callback(err) - }) - } - - function _remove (id, callback) { - fsCollection.remove(id, function (err) { - if (err) throw err - callback(err) - }) - } - - InsecureLogin.ready(function () { - reset() - _insert(function (nil, fileObj) { - _update(fileObj._id, function (nil) { - _remove(fileObj._id, function (nil) { - // update is called multiple times to update various properties. I - // have not nailed down exactly how many times as it seems to change - // between 3 and 4 for the size of gif I'm using in this test. - - console.log('counts:', JSON.stringify(counts)) - - test.equal(counts.before.insert, 1) - test.isTrue(counts.before.update > 0) - test.equal(counts.before.remove, 1) - - test.equal(counts.after.insert, 1) - test.isTrue(counts.before.update > 0) - test.equal(counts.after.remove, 1) - - next() - }) - }) - }) - }) -}) diff --git a/tests/direct.js b/tests/direct.js deleted file mode 100644 index 494c3c7..0000000 --- a/tests/direct.js +++ /dev/null @@ -1,156 +0,0 @@ -/* global Tinytest Meteor Mongo _ */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -_.each([null, 'direct_collection_test'], function (ctype) { - Tinytest.add('direct - hooks should not be fired when using .direct (collection type ' + ctype + ')', function (test) { - // console.log('-------', ctype) - - var collection = new Collection(ctype, {connection: null}) - var hookCount = 0 - - // The server will make a call to find when findOne is called, which adds 2 extra counts - // Update will make calls to find with options forwarded, which adds 4 extra counts - var hookCountTarget = Meteor.isServer ? 16 : 14 - - // Full permissions on collection - collection.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } - }) - - collection.before.insert(function (userId, doc) { - if (doc && doc.test) { - hookCount++ - // console.log(ctype, ': before insert', hookCount) - } - }) - - collection.after.insert(function (userId, doc) { - if (doc && doc.test) { - hookCount++ - // console.log(ctype, ': after insert', hookCount) - } - }) - - collection.before.update(function (userId, doc, fieldNames, modifier, options) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': before update', hookCount) - } - }) - - collection.after.update(function (userId, doc, fieldNames, modifier, options) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': after update', hookCount) - } - }) - - collection.before.remove(function (userId, doc) { - if (doc && doc._id === 'test') { - hookCount++ - // console.log(ctype, ': before remove', hookCount) - } - }) - - collection.after.remove(function (userId, doc) { - if (doc && doc._id === 'test') { - hookCount++ - // console.log(ctype, ': after remove', hookCount) - } - }) - - collection.before.find(function (userId, selector, options) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': before find', hookCount) - } - }) - - collection.after.find(function (userId, selector, options, result) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': after find', hookCount) - } - }) - - collection.before.findOne(function (userId, selector, options) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': before findOne', hookCount) - } - }) - - collection.after.findOne(function (userId, selector, options, result) { - if (options && options.test) { - hookCount++ - // console.log(ctype, ': after findOne', hookCount) - } - }) - - collection.insert({_id: 'test', test: 1}) - collection.update({_id: 'test'}, {$set: {test: 1}}, {test: 1}) - collection.find({}, {test: 1}) - collection.findOne({}, {test: 1}) - collection.remove({_id: 'test'}) - - test.equal(hookCount, hookCountTarget) - - // These should in no way affect the hookCount, which is essential in proving - // that the direct calls are functioning as intended - collection.direct.insert({_id: 'test', test: 1}) - - collection.direct.update({_id: 'test'}, {$set: {test: 1}}, {test: 1}) - - var cursor = collection.direct.find({}, {test: 1}) - var count = cursor.count() - test.equal(count, 1) - - var doc = collection.direct.findOne({}, {test: 1}) - test.equal(doc.test, 1) - - collection.direct.remove({_id: 'test'}) - - test.equal(hookCount, hookCountTarget) - }) -}) - -_.each([{}, {connection: null}], function (conntype, i) { - _.each([null, 'direct_collection_test_stringid'], function (ctype) { - var cname = ctype && (ctype + i) - Tinytest.add('direct - update and remove should allow removing by _id string (' + cname + ', ' + JSON.stringify(conntype) + ')', function (test) { - var collection = new Collection(cname, conntype) - - // Full permissions on collection - collection.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } - }) - - function hasCountAndTestValue (count, value) { - var cursor = collection.direct.find({_id: 'testid', test: value}) - test.equal(cursor.count(), count) - } - - collection.direct.remove({_id: 'testid'}) - collection.direct.insert({_id: 'testid', test: 1}) - hasCountAndTestValue(1, 1) - collection.direct.update('testid', {$set: {test: 2}}) - hasCountAndTestValue(1, 2) - collection.direct.remove('testid') - hasCountAndTestValue(0, 2) - }) - }) -}) - -if (Meteor.isServer) { - Tinytest.add('direct - Meteor.users.direct.insert should return _id, not an object', function (test) { - Meteor.users.remove('directinserttestid') - - var result = Meteor.users.direct.insert({_id: 'directinserttestid', test: 1}) - test.isFalse(_.isObject(result)) - }) -} diff --git a/tests/find_users.js b/tests/find_users.js deleted file mode 100644 index 6d93d89..0000000 --- a/tests/find_users.js +++ /dev/null @@ -1,71 +0,0 @@ -/* global Tinytest Meteor InsecureLogin _ */ - -Tinytest.addAsync('users - find hooks should be capable of being used on special Meteor.users collection', function (test, next) { - var aspect1 = Meteor.users.before.find(function (userId, selector, options) { - if (selector && selector.test) { - selector.a = 1 - } - }) - - var aspect2 = Meteor.users.after.find(function (userId, selector, options) { - if (selector && selector.test) { - selector.b = 1 - } - }) - - InsecureLogin.ready(function () { - var selector = {test: 1} - Meteor.users.find(selector) - test.equal(_.has(selector, 'a'), true) - test.equal(_.has(selector, 'b'), true) - aspect1.remove() - aspect2.remove() - - test.notEqual(Meteor.users.find().count(), 0) - - next() - }) -}) - -Tinytest.addAsync('users - find hooks should be capable of being used on wrapped Meteor.users collection', function (test, next) { - function TestUser (doc) { - return _.extend(this, doc) - } - - Meteor.users.__transform = function (doc) { return new TestUser(doc) } - - var MeteorUsersFind = Meteor.users.find - - Meteor.users.find = function (selector, options) { - selector = selector || {} - options = options || {} - return MeteorUsersFind.call(this, selector, _.extend({ transform: Meteor.users.__transform }, options)) - } - - var aspect1 = Meteor.users.before.find(function (userId, selector, options) { - if (selector && selector.test) { - selector.a = 1 - } - }) - - var aspect2 = Meteor.users.after.find(function (userId, selector, options) { - if (selector && selector.test) { - selector.b = 1 - } - }) - - InsecureLogin.ready(function () { - var selector = {test: 1} - Meteor.users.find(selector) - test.equal(_.has(selector, 'a'), true) - test.equal(_.has(selector, 'b'), true) - aspect1.remove() - aspect2.remove() - - test.notEqual(Meteor.users.find().count(), 0) - - Meteor.users.find = MeteorUsersFind - - next() - }) -}) diff --git a/tests/insecure_login.js b/tests/insecure_login.js deleted file mode 100644 index 3e9ad64..0000000 --- a/tests/insecure_login.js +++ /dev/null @@ -1,58 +0,0 @@ -/* global Meteor Accounts InsecureLogin _ */ -/* eslint-disable no-native-reassign, no-global-assign */ - -InsecureLogin = { - queue: [], - ran: false, - ready: function (callback) { - this.queue.push(callback) - if (this.ran) this.unwind() - }, - run: function () { - this.ran = true - this.unwind() - }, - unwind: function () { - _.each(this.queue, function (callback) { - callback() - }) - this.queue = [] - } -} - -if (Meteor.isClient) { - Accounts.callLoginMethod({ - methodArguments: [{username: 'InsecureLogin'}], - userCallback: function (err) { - if (err) throw err - console.info('Insecure login successful!') - InsecureLogin.run() - } - }) -} else { - InsecureLogin.run() -} - -if (Meteor.isServer) { - // Meteor.users.remove({'username': 'InsecureLogin'}) - - if (!Meteor.users.find({'username': 'InsecureLogin'}).count()) { - Accounts.createUser({ - username: 'InsecureLogin', - email: 'test@test.com', - password: 'password', - profile: {name: 'InsecureLogin'} - }) - } - - Accounts.registerLoginHandler(function (options) { - if (!options.username) return - - var user = Meteor.users.findOne({'username': options.username}) - if (!user) return - - return { - userId: user._id - } - }) -} diff --git a/tst/client/insecure_login.js b/tst/client/insecure_login.js new file mode 100644 index 0000000..99750c8 --- /dev/null +++ b/tst/client/insecure_login.js @@ -0,0 +1,15 @@ +import { Accounts } from 'meteor/accounts-base'; +import { InsecureLogin } from '../insecure_login'; + +Accounts.callLoginMethod({ + methodArguments: [{username: 'InsecureLogin'}], + userCallback(err) { + if (err) throw err + console.info('Insecure login successful!') + InsecureLogin.run() + } +}); + +export { + InsecureLogin +} \ No newline at end of file diff --git a/tst/client/main.js b/tst/client/main.js new file mode 100644 index 0000000..605be8a --- /dev/null +++ b/tst/client/main.js @@ -0,0 +1,2 @@ +import './insecure_login'; +import '../common'; diff --git a/tst/common.js b/tst/common.js new file mode 100644 index 0000000..ea37300 --- /dev/null +++ b/tst/common.js @@ -0,0 +1,23 @@ +import './insert_local.js' +import './insert_both.js' +import './insert_allow.js' +import './update_local.js' +import './update_both.js' +import './update_allow.js' +import './remove_local.js' +import './remove_both.js' +import './remove_allow.js' +import './find.js' +import './findone.js' +import './find_users.js' +import './find_findone_userid.js' +import './multiple_hooks.js' +import './transform.js' +import './direct.js' +import './optional_previous.js' +import './compat.js' +import './hooks_in_loop.js' +import './upsert.js' +import './trycatch.js' +import './meteor_1_4_id_object.js' + diff --git a/tests/compat.js b/tst/compat.js similarity index 77% rename from tests/compat.js rename to tst/compat.js index f66f2fc..80e3b2e 100644 --- a/tests/compat.js +++ b/tst/compat.js @@ -1,14 +1,8 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ -/* eslint-disable no-new */ +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -Tinytest.add('compat - legacy "new Meteor.Collection" should not throw an exception', function (test) { - try { - new Meteor.Collection(null) - test.ok() - } catch (e) { - test.fail(e.message) - } -}) +/* eslint-disable no-new */ Tinytest.add('compat - "new Mongo.Collection" should not throw an exception', function (test) { try { @@ -19,22 +13,18 @@ Tinytest.add('compat - "new Mongo.Collection" should not throw an exception', fu } }) -Tinytest.addAsync('compat - hooks should work for "new Meteor.Collection"', function (test, next) { - simpleCountTest(new Meteor.Collection(null), test, next) -}) - Tinytest.addAsync('compat - hooks should work for "new Mongo.Collection"', function (test, next) { simpleCountTest(new Mongo.Collection(null), test, next) }) function simpleCountTest (collection, test, next) { collection.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } + insert() { return true }, + update() { return true }, + remove() { return true } }) - var counts = { + const counts = { before: { insert: 0, update: 0, diff --git a/tst/direct.js b/tst/direct.js new file mode 100644 index 0000000..cb208e1 --- /dev/null +++ b/tst/direct.js @@ -0,0 +1,160 @@ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; + +// XXX: Code below throws +// TypeError: Cannot read property '#' of undefined +// No idea why... + +// ([null, 'direct_collection_test']).forEach(function (ctype) { +// Tinytest.add(`direct - hooks should not be fired when using .direct (collection type ${ctype})`, function (test) { +// // console.log('-------', ctype) + +// const collection = new Mongo.Collection(ctype, {connection: null}) +// let hookCount = 0 + +// // The server will make a call to find when findOne is called, which adds 2 extra counts +// // Update will make calls to find with options forwarded, which adds 4 extra counts +// const hookCountTarget = Meteor.isServer ? 16 : 14 + +// // Full permissions on collection +// collection.allow({ +// insert: function () { return true }, +// update: function () { return true }, +// remove: function () { return true } +// }) + +// collection.before.insert(function (userId, doc) { +// if (doc && doc.test) { +// hookCount++ +// // console.log(ctype, ': before insert', hookCount) +// } +// }) + +// collection.after.insert(function (userId, doc) { +// if (doc && doc.test) { +// hookCount++ +// // console.log(ctype, ': after insert', hookCount) +// } +// }) + +// collection.before.update(function (userId, doc, fieldNames, modifier, options) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': before update', hookCount) +// } +// }) + +// collection.after.update(function (userId, doc, fieldNames, modifier, options) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': after update', hookCount) +// } +// }) + +// collection.before.remove(function (userId, doc) { +// if (doc && doc._id === 'test') { +// hookCount++ +// // console.log(ctype, ': before remove', hookCount) +// } +// }) + +// collection.after.remove(function (userId, doc) { +// if (doc && doc._id === 'test') { +// hookCount++ +// // console.log(ctype, ': after remove', hookCount) +// } +// }) + +// collection.before.find(function (userId, selector, options) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': before find', hookCount) +// } +// }) + +// collection.after.find(function (userId, selector, options, result) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': after find', hookCount) +// } +// }) + +// collection.before.findOne(function (userId, selector, options) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': before findOne', hookCount) +// } +// }) + +// collection.after.findOne(function (userId, selector, options, result) { +// if (options && options.test) { +// hookCount++ +// // console.log(ctype, ': after findOne', hookCount) +// } +// }) + +// collection.insert({_id: 'test', test: 1}) +// collection.update({_id: 'test'}, {$set: {test: 1}}, {test: 1}) +// collection.find({}, {test: 1}) +// collection.findOne({}, {test: 1}) +// collection.remove({_id: 'test'}) + +// test.equal(hookCount, hookCountTarget) + +// // These should in no way affect the hookCount, which is essential in proving +// // that the direct calls are functioning as intended +// collection.direct.insert({_id: 'test', test: 1}) + +// collection.direct.update({_id: 'test'}, {$set: {test: 1}}, {test: 1}) + +// const cursor = collection.direct.find({}, {test: 1}) +// const count = cursor.count() +// test.equal(count, 1) + +// const doc = collection.direct.findOne({}, {test: 1}) +// test.equal(doc.test, 1) + +// collection.direct.remove({_id: 'test'}) + +// test.equal(hookCount, hookCountTarget) +// }) +// }) + +[{}, {connection: null}].forEach(function (conntype, i) { + [null, 'direct_collection_test_stringid'].forEach(function (ctype) { + const cname = ctype && (ctype + i) + Tinytest.add(`direct - update and remove should allow removing by _id string (${cname}, ${JSON.stringify(conntype)})`, function (test) { + const collection = new Mongo.Collection(cname, conntype) + + // Full permissions on collection + collection.allow({ + insert: function () { return true }, + update: function () { return true }, + remove: function () { return true } + }) + + function hasCountAndTestValue (count, value) { + const cursor = collection.direct.find({_id: 'testid', test: value}) + test.equal(cursor.count(), count) + } + + collection.direct.remove({_id: 'testid'}) + collection.direct.insert({_id: 'testid', test: 1}) + hasCountAndTestValue(1, 1) + collection.direct.update('testid', {$set: {test: 2}}) + hasCountAndTestValue(1, 2) + collection.direct.remove('testid') + hasCountAndTestValue(0, 2) + }) + }) +}) + +if (Meteor.isServer) { + Tinytest.add('direct - Meteor.users.direct.insert should return _id, not an object', function (test) { + Meteor.users.remove('directinserttestid') + + const result = Meteor.users.direct.insert({_id: 'directinserttestid', test: 1}) + test.isFalse(Object(result) === result); + }) +} diff --git a/tests/find.js b/tst/find.js similarity index 80% rename from tests/find.js rename to tst/find.js index 489bfbf..a4784e5 100644 --- a/tests/find.js +++ b/tst/find.js @@ -1,9 +1,9 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('find - selector should be {} when called without arguments', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.before.find(function (userId, selector, options) { test.equal(selector, {}) @@ -14,7 +14,7 @@ Tinytest.addAsync('find - selector should be {} when called without arguments', }) Tinytest.addAsync('find - selector should have extra property', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.before.find(function (userId, selector, options) { if (options && options.test) { @@ -33,8 +33,8 @@ Tinytest.addAsync('find - selector should have extra property', function (test, }) Tinytest.addAsync('find - tmp variable should have property added after the find', function (test, next) { - var collection = new Collection(null) - var tmp = {} + const collection = new Mongo.Collection(null) + const tmp = {} collection.after.find(function (userId, selector, options) { if (options && options.test) { diff --git a/tests/find_findone_userid.js b/tst/find_findone_userid.js similarity index 71% rename from tests/find_findone_userid.js rename to tst/find_findone_userid.js index 67ee73b..58c3eeb 100644 --- a/tests/find_findone_userid.js +++ b/tst/find_findone_userid.js @@ -1,17 +1,18 @@ -/* global Tinytest Meteor Mongo InsecureLogin CollectionHooks */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -var collection = new Collection('test_collection_for_find_findone_userid') - -var beforeFindUserId -var afterFindUserId -var beforeFindOneUserId -var afterFindOneUserId -var beforeFindWithinPublish -var afterFindWithinPublish -var beforeFindOneWithinPublish -var afterFindOneWithinPublish +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; + +const collection = new Mongo.Collection('test_collection_for_find_findone_userid') + +let beforeFindUserId +let afterFindUserId +let beforeFindOneUserId +let afterFindOneUserId +let beforeFindWithinPublish +let afterFindWithinPublish +let beforeFindOneWithinPublish +let afterFindOneWithinPublish // Don't declare hooks in publish method, as it is problematic collection.before.find(function (userId, selector, options) { @@ -55,8 +56,8 @@ collection.after.findOne(function (userId, selector, options, result) { }) if (Meteor.isServer) { - var serverTestsAdded = false - var publishContext = null + let serverTestsAdded = false + let publishContext = null Tinytest.add('general - isWithinPublish is false outside of publish function', function (test) { test.equal(CollectionHooks.isWithinPublish(), false) @@ -115,31 +116,23 @@ if (Meteor.isServer) { } if (Meteor.isClient) { - (function () { - function cleanup () { + function cleanup () { beforeFindUserId = null afterFindUserId = null beforeFindOneUserId = null afterFindOneUserId = null - } - - function withLogin (testFunc) { - return function () { - // grab arguments passed to testFunc (i.e. 'test') - var context = this - var args = arguments + } - function wrapper (cb) { - InsecureLogin.ready(function () { + function withLogin (testFunc) { + return function (...args) { + const wrapper = (cb) => { + InsecureLogin.ready(() => { cleanup() - var err - try { - var result = testFunc.apply(context, args) + var result = testFunc.apply(this, args) cb(null, result) } catch (error) { - err = error - cb(err) + cb(error) } finally { cleanup() } @@ -148,36 +141,34 @@ if (Meteor.isClient) { return Meteor.wrapAsync(wrapper) // Don't run this function, just wrap it } - } - - // Run client tests. - // TODO: Somehow, Tinytest.add / addAsync doesn't work inside InsecureLogin.ready(). - // Hence, we add these tests wrapped synchronously with a login hook. + } - // Ideally, this function should wrap the test functions. - Tinytest.add('find - userId available to before find hook', withLogin(function (test) { + // Run client tests. + // TODO: Somehow, Tinytest.add / addAsync doesn't work inside InsecureLogin.ready(). + // Hence, we add these tests wrapped synchronously with a login hook. + // Ideally, this function should wrap the test functions. + Tinytest.add('find - userId available to before find hook', withLogin(function (test) { collection.find({}, {test: 1}) test.notEqual(beforeFindUserId, null) - })) + })) - Tinytest.add('find - userId available to after find hook', withLogin(function (test) { + Tinytest.add('find - userId available to after find hook', withLogin(function (test) { collection.find({}, {test: 1}) test.notEqual(afterFindUserId, null) - })) + })) - Tinytest.add('findone - userId available to before findOne hook', withLogin(function (test) { + Tinytest.add('findone - userId available to before findOne hook', withLogin(function (test) { collection.findOne({}, {test: 1}) test.notEqual(beforeFindOneUserId, null) - })) + })) - Tinytest.add('findone - userId available to after findOne hook', withLogin(function (test) { + Tinytest.add('findone - userId available to after findOne hook', withLogin(function (test) { collection.findOne({}, {test: 1}) test.notEqual(afterFindOneUserId, null) - })) + })) - InsecureLogin.ready(function () { + InsecureLogin.ready(function () { // Run server tests Meteor.subscribe('test_publish_for_find_findone_userid') - }) - })() + }) } diff --git a/tst/find_users.js b/tst/find_users.js new file mode 100644 index 0000000..508e27e --- /dev/null +++ b/tst/find_users.js @@ -0,0 +1,71 @@ +import { Meteor } from 'meteor/meteor'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; + +Tinytest.addAsync('users - find hooks should be capable of being used on special Meteor.users collection', function (test, next) { + const aspect1 = Meteor.users.before.find(function (userId, selector, options) { + if (selector && selector.test) { + selector.a = 1 + } + }) + + const aspect2 = Meteor.users.after.find(function (userId, selector, options) { + if (selector && selector.test) { + selector.b = 1 + } + }) + + InsecureLogin.ready(function () { + const selector = {test: 1} + Meteor.users.find(selector) + test.equal(selector.hasOwnProperty('a'), true) + test.equal(selector.hasOwnProperty('b'), true) + aspect1.remove() + aspect2.remove() + + test.notEqual(Meteor.users.find().count(), 0) + + next() + }) +}) + +Tinytest.addAsync('users - find hooks should be capable of being used on wrapped Meteor.users collection', function (test, next) { + function TestUser (doc) { + return Object.assign(this, doc) + } + + Meteor.users.__transform = doc => new TestUser(doc); + + const MeteorUsersFind = Meteor.users.find + + Meteor.users.find = function (selector = {}, options = {}) { + return MeteorUsersFind.call(this, selector, { transform: Meteor.users.__transform, ...options }) + } + + const aspect1 = Meteor.users.before.find(function (userId, selector, options) { + if (selector && selector.test) { + selector.a = 1 + } + }) + + const aspect2 = Meteor.users.after.find(function (userId, selector, options) { + if (selector && selector.test) { + selector.b = 1 + } + }) + + InsecureLogin.ready(function () { + const selector = { test: 1 } + Meteor.users.find(selector) + test.equal(selector.hasOwnProperty('a'), true) + test.equal(selector.hasOwnProperty('b'), true) + aspect1.remove() + aspect2.remove() + + test.notEqual(Meteor.users.find().count(), 0) + + Meteor.users.find = MeteorUsersFind + + next() + }) +}) diff --git a/tests/findone.js b/tst/findone.js similarity index 80% rename from tests/findone.js rename to tst/findone.js index a434e65..f062de6 100644 --- a/tests/findone.js +++ b/tst/findone.js @@ -1,9 +1,9 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('findone - selector should be {} when called without arguments', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.before.findOne(function (userId, selector, options) { test.equal(selector, {}) @@ -14,7 +14,7 @@ Tinytest.addAsync('findone - selector should be {} when called without arguments }) Tinytest.addAsync('findone - selector should have extra property', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.before.findOne(function (userId, selector, options) { if (options && options.test) { @@ -33,8 +33,8 @@ Tinytest.addAsync('findone - selector should have extra property', function (tes }) Tinytest.addAsync('findone - tmp variable should have property added after the find', function (test, next) { - var collection = new Collection(null) - var tmp = {} + const collection = new Mongo.Collection(null) + const tmp = {} collection.after.findOne(function (userId, selector, options) { if (options && options.test) { diff --git a/tests/hooks_in_loop.js b/tst/hooks_in_loop.js similarity index 82% rename from tests/hooks_in_loop.js rename to tst/hooks_in_loop.js index a4031c0..9e0075e 100644 --- a/tests/hooks_in_loop.js +++ b/tst/hooks_in_loop.js @@ -1,12 +1,13 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -var collection = new Collection('test_hooks_in_loop') -var times = 30 +const collection = new Mongo.Collection('test_hooks_in_loop') +let times = 30 if (Meteor.isServer) { - var s1 = 0 + let s1 = 0 // full client-side access collection.allow({ @@ -36,8 +37,8 @@ if (Meteor.isClient) { Meteor.subscribe('test_hooks_in_loop_publish_collection') Tinytest.addAsync('issue #67 - hooks should get called when mutation method called in a tight loop', function (test, next) { - var c1 = 0 - var c2 = 0 + let c1 = 0 + let c2 = 0 collection.before.update(function (userId, doc, fieldNames, modifier) { c1++ @@ -47,7 +48,7 @@ if (Meteor.isClient) { InsecureLogin.ready(function () { Meteor.call('test_hooks_in_loop_reset_collection', function (nil, result) { function start (id) { - for (var i = 0; i < times; i++) { + for (let i = 0; i < times; i++) { collection.update({_id: id}, {$set: {times: times}}, function (nil) { c2++ check() diff --git a/tst/insecure_login.js b/tst/insecure_login.js new file mode 100644 index 0000000..0f67dad --- /dev/null +++ b/tst/insecure_login.js @@ -0,0 +1,18 @@ +/* eslint-disable no-native-reassign, no-global-assign */ + +export const InsecureLogin = { + queue: [], + ran: false, + ready: function (callback) { + this.queue.push(callback) + if (this.ran) this.unwind() + }, + run: function () { + this.ran = true + this.unwind() + }, + unwind: function () { + this.queue.forEach(cb => cb()); + this.queue = [] + } +} diff --git a/tests/insert_allow.js b/tst/insert_allow.js similarity index 76% rename from tests/insert_allow.js rename to tst/insert_allow.js index 3693537..ab59fd0 100644 --- a/tests/insert_allow.js +++ b/tst/insert_allow.js @@ -1,15 +1,16 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -var collection = new Collection('test_insert_allow_collection') +const collection = new Mongo.Collection('test_insert_allow_collection') if (Meteor.isServer) { // full client-side access collection.allow({ - insert: function (userId, doc) { return doc.allowed }, - update: function () { return true }, - remove: function () { return true } + insert(userId, doc) { return doc.allowed }, + update(){ return true; }, + remove() { return true } }) Meteor.methods({ diff --git a/tests/insert_both.js b/tst/insert_both.js similarity index 85% rename from tests/insert_both.js rename to tst/insert_both.js index d23fc33..1d63d64 100644 --- a/tests/insert_both.js +++ b/tst/insert_both.js @@ -1,12 +1,13 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; if (Meteor.isServer) { - var collection1 = new Collection('test_insert_collection1') + const collection1 = new Mongo.Collection('test_insert_collection1') Tinytest.addAsync('insert - collection1 document should have extra property added to it before it is inserted', function (test, next) { - var tmp = {} + const tmp = {} collection1.remove({}) @@ -25,14 +26,14 @@ if (Meteor.isServer) { }) } -var collection2 = new Collection('test_insert_collection2') +var collection2 = new Mongo.Collection('test_insert_collection2') if (Meteor.isServer) { // full client-side access collection2.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } + insert() { return true }, + update() { return true }, + remove() { return true } }) Meteor.methods({ diff --git a/tests/insert_local.js b/tst/insert_local.js similarity index 83% rename from tests/insert_local.js rename to tst/insert_local.js index 781cb10..fe429b7 100644 --- a/tests/insert_local.js +++ b/tst/insert_local.js @@ -1,10 +1,11 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('insert - local collection document should have extra property added before being inserted', function (test, next) { - var collection = new Collection(null) - var tmp = {} + const collection = new Mongo.Collection(null) + const tmp = {} collection.before.insert(function (userId, doc) { tmp.typeof_userId = typeof userId @@ -26,7 +27,7 @@ Tinytest.addAsync('insert - local collection document should have extra property }) Tinytest.addAsync('insert - local collection should fire after-insert hook', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.after.insert(function (userId, doc) { if (Meteor.isServer) { diff --git a/tests/meteor_1_4_id_object.js b/tst/meteor_1_4_id_object.js similarity index 68% rename from tests/meteor_1_4_id_object.js rename to tst/meteor_1_4_id_object.js index b111e3d..1f1711e 100644 --- a/tests/meteor_1_4_id_object.js +++ b/tst/meteor_1_4_id_object.js @@ -1,8 +1,9 @@ -/* global Tinytest Meteor _ Mongo */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection -var collection = Meteor.users -var collection1 = new Collection('test_insert_mongoid_collection1', {idGeneration: 'MONGO'}) +const collection = Meteor.users +const collection1 = new Mongo.Collection('test_insert_mongoid_collection1', {idGeneration: 'MONGO'}) if (Meteor.isServer) { collection.allow({ @@ -17,12 +18,12 @@ if (Meteor.isServer) { } Tinytest.addAsync('meteor_1_4_id_object - after insert hooks should be able to cope with object _id with ops property in Meteor 1.4', function (test, next) { - var key = Date.now() + const key = Date.now() - var aspect1 = collection.after.insert(function (nil, doc) { + const aspect1 = collection.after.insert(function (nil, doc) { if (doc && doc.key && doc.key === key) { test.equal(doc._id, this._id) - test.isFalse(_.isObject(doc._id), '_id property should not be an object') + test.isFalse(Object(doc._id) === doc._id, '_id property should not be an object') } }) @@ -38,11 +39,11 @@ Tinytest.addAsync('meteor_1_4_id_object - after insert hooks should be able to c }) Tinytest.addAsync('meteor_1_4_id_object - after insert hooks should be able to cope with Mongo.ObjectID _id with _str property in Meteor 1.4', function (test, next) { - var key = Date.now() + const key = Date.now() - var aspect1 = collection1.after.insert(function (nil, doc) { + const aspect1 = collection1.after.insert(function (nil, doc) { if (doc && doc.key && doc.key === key) { - var foundDoc = null + let foundDoc = null try { foundDoc = collection1.direct.findOne({_id: doc._id}) } catch (exception) {} diff --git a/tests/multiple_hooks.js b/tst/multiple_hooks.js similarity index 87% rename from tests/multiple_hooks.js rename to tst/multiple_hooks.js index c81047d..a626167 100644 --- a/tests/multiple_hooks.js +++ b/tst/multiple_hooks.js @@ -1,10 +1,10 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('general - multiple hooks should all fire the appropriate number of times', function (test, next) { - var collection = new Collection(null) - var counts = { + const collection = new Mongo.Collection(null) + const counts = { before: { insert: 0, update: 0, diff --git a/tests/optional_previous.js b/tst/optional_previous.js similarity index 89% rename from tests/optional_previous.js rename to tst/optional_previous.js index 1bfd171..9ea11c5 100644 --- a/tests/optional_previous.js +++ b/tst/optional_previous.js @@ -1,9 +1,9 @@ -/* global Tinytest Meteor Mongo CollectionHooks */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; Tinytest.addAsync('optional-previous - update hook should not prefetch previous, via hook option param', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.after.update(function (userId, doc, fieldNames, modifier, options) { if (doc && doc._id === 'test') { @@ -18,7 +18,7 @@ Tinytest.addAsync('optional-previous - update hook should not prefetch previous, }) Tinytest.addAsync('optional-previous - update hook should not prefetch previous, via collection option param', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.hookOptions.after.update = {fetchPrevious: false} @@ -44,7 +44,7 @@ if (Meteor.isServer) { // acceptable in this case. Tinytest.add('optional-previous - update hook should not prefetch previous, via defaults param variation 1: after.update', function (test) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) CollectionHooks.defaults.after.update = {fetchPrevious: false} @@ -61,7 +61,7 @@ if (Meteor.isServer) { }) Tinytest.add('optional-previous - update hook should not prefetch previous, via defaults param variation 2: after.all', function (test) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) CollectionHooks.defaults.after.all = {fetchPrevious: false} @@ -78,7 +78,7 @@ if (Meteor.isServer) { }) Tinytest.add('optional-previous - update hook should not prefetch previous, via defaults param variation 3: all.update', function (test) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) CollectionHooks.defaults.all.update = {fetchPrevious: false} @@ -95,7 +95,7 @@ if (Meteor.isServer) { }) Tinytest.add('optional-previous - update hook should not prefetch previous, via defaults param variation 4: all.all', function (test) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) CollectionHooks.defaults.all.all = {fetchPrevious: false} diff --git a/tests/remove_allow.js b/tst/remove_allow.js similarity index 77% rename from tests/remove_allow.js rename to tst/remove_allow.js index 9baaae5..35f81b1 100644 --- a/tests/remove_allow.js +++ b/tst/remove_allow.js @@ -1,15 +1,16 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -var collection = new Collection('test_remove_allow_collection') +const collection = new Mongo.Collection('test_remove_allow_collection') if (Meteor.isServer) { // full client-side access collection.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function (userId, doc) { return doc.allowed } + insert() { return true }, + update() { return true }, + remove(userId, doc) { return doc.allowed } }) Meteor.methods({ diff --git a/tests/remove_both.js b/tst/remove_both.js similarity index 89% rename from tests/remove_both.js rename to tst/remove_both.js index 2a7dff1..4851e15 100644 --- a/tests/remove_both.js +++ b/tst/remove_both.js @@ -1,13 +1,14 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; if (Meteor.isServer) { - var collection1 = new Collection('test_remove_collection1') - var external = false + const collection1 = new Mongo.Collection('test_remove_collection1') + let external = false Tinytest.addAsync('remove - collection1 document should affect external variable before it is removed', function (test, next) { - var tmp = {} + const tmp = {} function start (nil, id) { collection1.before.remove(function (userId, doc) { @@ -33,7 +34,7 @@ if (Meteor.isServer) { }) } -var collection2 = new Collection('test_remove_collection2') +const collection2 = new Mongo.Collection('test_remove_collection2') if (Meteor.isServer) { // full client-side access @@ -54,7 +55,7 @@ if (Meteor.isServer) { }) // Tinytest.addAsync('remove - collection2 document should affect external variable before and after it is removed', function (test, next) { - var external2 = -1 + let external2 = -1 collection2.before.remove(function (userId, doc) { // Remove is initiated by a client, a userId must be present @@ -87,9 +88,9 @@ if (Meteor.isClient) { Meteor.subscribe('test_remove_publish_collection2') Tinytest.addAsync('remove - collection2 document should affect external variable before and after it is removed', function (test, next) { - var external = 0 - var c = 0 - var n = function () { + let external = 0 + let c = 0 + const n = () => { if (++c === 2) { test.equal(external, 2) next() diff --git a/tests/remove_local.js b/tst/remove_local.js similarity index 84% rename from tests/remove_local.js rename to tst/remove_local.js index 5192441..bd2138d 100644 --- a/tests/remove_local.js +++ b/tst/remove_local.js @@ -1,12 +1,13 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('remove - local collection document should affect external variable before being removed', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) function start (nil, id) { - var external = 0 + let external = 0 collection.before.remove(function (userId, doc) { // There should be a userId if we're running on the client. @@ -35,11 +36,11 @@ Tinytest.addAsync('remove - local collection document should affect external var }) Tinytest.addAsync('remove - local collection should fire after-remove hook and affect external variable', function (test, next) { - var collection = new Collection(null) - var external = 0 + const collection = new Mongo.Collection(null) + let external = 0 - var c = 0 - var n = function () { + let c = 0 + const n = function () { if (++c === 2) { test.equal(external, 1) next() diff --git a/tests/fetch.js b/tst/server/fetch.js similarity index 60% rename from tests/fetch.js rename to tst/server/fetch.js index 6567f5d..b53729a 100644 --- a/tests/fetch.js +++ b/tst/server/fetch.js @@ -1,19 +1,18 @@ -/* global Tinytest Meteor Mongo InsecureLogin _ */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; Tinytest.addAsync('general - local collection documents should only have fetched fields', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) function same (arr1, arr2) { - return arr1.length === arr2.length && _.intersection(arr1, arr2).length === arr1.length + return arr1.length === arr2.length && arr1.every(el => arr2.includes(el)) } function start (nil, id) { - var fields = ['fetch_value1', 'fetch_value2'] + const fields = ['fetch_value1', 'fetch_value2'] collection.after.update(function (userId, doc, fieldNames, modifier) { - var docKeys = _.without(_.keys(doc), '_id') + let { _id, ...docKeys } = Object.keys(doc); test.equal(same(docKeys, fields), true) next() }, { diff --git a/tst/server/insecure_login.js b/tst/server/insecure_login.js new file mode 100644 index 0000000..703a651 --- /dev/null +++ b/tst/server/insecure_login.js @@ -0,0 +1,28 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; +import { InsecureLogin } from '../insecure_login'; + +InsecureLogin.run(); + +// Meteor.users.remove({'username': 'InsecureLogin'}) +if (!Meteor.users.find({'username': 'InsecureLogin'}).count()) { + Accounts.createUser({ + username: 'InsecureLogin', + email: 'test@test.com', + password: 'password', + profile: {name: 'InsecureLogin'} + }) +} + +Accounts.registerLoginHandler(function (options) { + if (!options.username) return + const user = Meteor.users.findOne({'username': options.username}) + if (!user) return + return { + userId: user._id + } +}) + +export { + InsecureLogin +}; diff --git a/tests/insert_user.js b/tst/server/insert_user.js similarity index 68% rename from tests/insert_user.js rename to tst/server/insert_user.js index 20f947f..961651d 100644 --- a/tests/insert_user.js +++ b/tst/server/insert_user.js @@ -1,18 +1,19 @@ -/* global Tinytest Meteor _ */ +import { Meteor } from 'meteor/meteor'; +import { Tinytest } from 'meteor/tinytest'; Tinytest.addAsync('insert - Meteor.users collection document should have extra property added before being inserted and properly provide inserted _id in after hook', function (test, next) { - var collection = Meteor.users + const collection = Meteor.users - var aspect1 = collection.before.insert(function (nil, doc) { + const aspect1 = collection.before.insert(function (nil, doc) { if (doc && doc.test) { doc.before_insert_value = true } }) - var aspect2 = collection.after.insert(function (nil, doc) { + const aspect2 = collection.after.insert(function (nil, doc) { if (doc && doc.test) { test.equal(doc._id, this._id) - test.isFalse(_.isArray(doc._id)) + test.isFalse(Array.isArray(doc._id)) } }) diff --git a/tst/server/main.js b/tst/server/main.js new file mode 100644 index 0000000..26c53dc --- /dev/null +++ b/tst/server/main.js @@ -0,0 +1,12 @@ +import './insecure_login'; + +import '../common'; + +import './insert_user.js' +import './update_user.js' +import './update_without_id.js' + +// NOTE: not supporting fetch for the time being. +// NOTE: fetch can only work server-side because find's 'fields' option is +// limited to only working on the server +// import './fetch.js' diff --git a/tests/update_user.js b/tst/server/update_user.js similarity index 73% rename from tests/update_user.js rename to tst/server/update_user.js index e25797a..a176146 100644 --- a/tests/update_user.js +++ b/tst/server/update_user.js @@ -1,16 +1,18 @@ -/* global Tinytest Meteor InsecureLogin */ +import { Meteor } from 'meteor/meteor'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('update - Meteor.users collection document should have extra property added before being updated', function (test, next) { - var collection = Meteor.users + const collection = Meteor.users function start () { - var aspect1 = collection.before.update(function (userId, doc, fieldNames, modifier) { + const aspect1 = collection.before.update(function (userId, doc, fieldNames, modifier) { if (modifier && modifier.$set && modifier.$set.test) { modifier.$set.before_update_value = true } }) - var aspect2 = collection.after.update(function (userId, doc, fieldNames, modifier, options) { + const aspect2 = collection.after.update(function (userId, doc, fieldNames, modifier, options) { test.isTrue(modifier !== undefined && options !== undefined, 'modifier and options should not be undefined when fetchPrevious is false issue #97 and #138') }, {fetchPrevious: false}) @@ -25,7 +27,7 @@ Tinytest.addAsync('update - Meteor.users collection document should have extra p }) } - var user = collection.findOne({test: 2}) + const user = collection.findOne({test: 2}) if (!user) { collection.insert({test: 2}, function (err, id) { diff --git a/tests/update_without_id.js b/tst/server/update_without_id.js similarity index 79% rename from tests/update_without_id.js rename to tst/server/update_without_id.js index 903e348..c5ce37f 100644 --- a/tests/update_without_id.js +++ b/tst/server/update_without_id.js @@ -1,28 +1,28 @@ -/* global Tinytest Meteor Mongo _ */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; Tinytest.addAsync('update - server collection documents should have extra properties added before and after being updated despite selector not being _id', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) - var retries = 0 - var retry = function (func, expect, cb) { + let retries = 0 + const retry = function (func, expect, cb) { if (++retries >= 5) return Meteor.bindEnvironment(cb) Meteor.setTimeout(function () { - var r = func() + const r = func() if (expect(r)) return cb(r) retry(func, expect, cb) }, 100) } collection.before.update(function (userId, doc, fieldNames, modifier, options) { - if (_.contains(fieldNames, 'test')) { + if (fieldNames.includes('test')) { modifier.$set.before_update_value = true } }) collection.after.update(function (userId, doc, fieldNames, modifier, options) { - if (_.contains(fieldNames, 'test')) { + if (fieldNames.includes('test')) { collection.update({_id: doc._id}, {$set: {after_update_value: true}}) } }) diff --git a/tests/transform.js b/tst/transform.js similarity index 51% rename from tests/transform.js rename to tst/transform.js index 1b38291..6c59532 100644 --- a/tests/transform.js +++ b/tst/transform.js @@ -1,21 +1,21 @@ -/* global Tinytest Meteor Mongo InsecureLogin _ */ +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +const isFunction = (fn) => typeof fn === 'function'; Tinytest.addAsync('general - hook callbacks should have this.transform function that works', function (test, next) { - var collection = new Collection(null, { - transform: function (doc) { - return _.extend(doc, {isTransformed: true}) - } + const collection = new Mongo.Collection(null, { + transform: doc => ({ ...doc, isTransformed: true }) }) collection.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } + insert() { return true }, + update() { return true }, + remove() { return true } }) - var counts = { + const counts = { before: { insert: 0, update: 0, @@ -28,13 +28,13 @@ Tinytest.addAsync('general - hook callbacks should have this.transform function } } - collection.before.insert(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.before.insert++ } }) - collection.before.update(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.before.update++ } }) - collection.before.remove(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.before.remove++ } }) + collection.before.insert(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.before.insert++ } }) + collection.before.update(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.before.update++ } }) + collection.before.remove(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.before.remove++ } }) - collection.after.insert(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.after.insert++ } }) - collection.after.update(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.after.update++ } }) - collection.after.remove(function (userId, doc) { if (_.isFunction(this.transform) && this.transform().isTransformed) { counts.after.remove++ } }) + collection.after.insert(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.after.insert++ } }) + collection.after.update(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.after.update++ } }) + collection.after.remove(function (userId, doc) { if (isFunction(this.transform) && this.transform().isTransformed) { counts.after.remove++ } }) InsecureLogin.ready(function () { // TODO: does it make sense to pass an _id on insert just to get this test diff --git a/tests/trycatch.js b/tst/trycatch.js similarity index 77% rename from tests/trycatch.js rename to tst/trycatch.js index 4345f88..6ce49b0 100644 --- a/tests/trycatch.js +++ b/tst/trycatch.js @@ -1,10 +1,10 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('try-catch - should call error callback on insert hook exception', function (test, next) { - var collection = new Collection(null) - var msg = 'insert hook test error' + const collection = new Mongo.Collection(null) + const msg = 'insert hook test error' collection.before.insert(function (userId, doc) { throw new Error(msg) @@ -23,8 +23,8 @@ Tinytest.addAsync('try-catch - should call error callback on insert hook excepti }) Tinytest.addAsync('try-catch - should call error callback on update hook exception', function (test, next) { - var collection = new Collection(null) - var msg = 'update hook test error' + const collection = new Mongo.Collection(null) + const msg = 'update hook test error' collection.before.update(function (userId, doc) { throw new Error(msg) @@ -45,8 +45,8 @@ Tinytest.addAsync('try-catch - should call error callback on update hook excepti }) Tinytest.addAsync('try-catch - should call error callback on remove hook exception', function (test, next) { - var collection = new Collection(null) - var msg = 'remove hook test error' + const collection = new Mongo.Collection(null) + const msg = 'remove hook test error' collection.before.remove(function (userId, doc) { throw new Error(msg) diff --git a/tests/update_allow.js b/tst/update_allow.js similarity index 79% rename from tests/update_allow.js rename to tst/update_allow.js index 8344835..b16f834 100644 --- a/tests/update_allow.js +++ b/tst/update_allow.js @@ -1,15 +1,16 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - -var collection = new Collection('test_update_allow_collection') +const collection = new Mongo.Collection('test_update_allow_collection') if (Meteor.isServer) { // full client-side access collection.allow({ - insert: function () { return true }, - update: function (userId, doc, fieldNames, modifier) { return modifier.$set.allowed }, - remove: function () { return true } + insert() { return true }, + update(userId, doc, fieldNames, modifier) { return modifier.$set.allowed }, + remove() { return true } }) Meteor.methods({ diff --git a/tests/update_both.js b/tst/update_both.js similarity index 78% rename from tests/update_both.js rename to tst/update_both.js index 63ed748..4a4e4be 100644 --- a/tests/update_both.js +++ b/tst/update_both.js @@ -1,12 +1,14 @@ -/* global Tinytest Meteor Mongo InsecureLogin _ */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection -var collection1 = new Collection('test_update_collection1') +const collection1 = new Mongo.Collection('test_update_collection1') if (Meteor.isServer) { Tinytest.addAsync('update - collection1 document should have extra property added to it before it is updated', function (test, next) { - var tmp = {} + const tmp = {} function start () { collection1.before.update(function (userId, doc, fieldNames, modifier) { @@ -35,25 +37,23 @@ if (Meteor.isServer) { }) } -var collection2 = new Collection('test_update_collection2') +const collection2 = new Mongo.Collection('test_update_collection2') if (Meteor.isServer) { // full client-side access collection2.allow({ - insert: function () { return true }, - update: function () { return true }, - remove: function () { return true } + insert() { return true }, + update() { return true }, + remove() { return true } }) Meteor.methods({ - test_update_reset_collection2: function () { + test_update_reset_collection2() { collection2.remove({}) } }) - Meteor.publish('test_update_publish_collection2', function () { - return collection2.find() - }) + Meteor.publish('test_update_publish_collection2', () => collection2.find()) collection2.before.update(function (userId, doc, fieldNames, modifier) { modifier.$set.server_value = true @@ -64,8 +64,8 @@ if (Meteor.isClient) { Meteor.subscribe('test_update_publish_collection2') Tinytest.addAsync('update - collection2 document should have client-added and server-added extra properties added to it before it is updated', function (test, next) { - var c = 0 - var n = function () { if (++c === 2) { next() } } + let c = 0 + const n = () => { if (++c === 2) { next() } } function start (err, id) { if (err) throw err @@ -82,7 +82,7 @@ if (Meteor.isClient) { collection2.after.update(function (userId, doc, fieldNames, modifier) { test.equal(doc.update_value, true) - test.equal(_.has(this.previous, 'update_value'), false) + test.equal(this.previous.hasOwnProperty('update_value'), false) n() }) diff --git a/tests/update_local.js b/tst/update_local.js similarity index 85% rename from tests/update_local.js rename to tst/update_local.js index f142c72..1336b16 100644 --- a/tests/update_local.js +++ b/tst/update_local.js @@ -1,9 +1,10 @@ -/* global Tinytest Meteor Mongo InsecureLogin _ */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('update - local collection documents should have extra property added before being updated', function (test, next) { - var collection = new Collection(null) + var collection = new Mongo.Collection(null) function start () { collection.before.update(function (userId, doc, fieldNames, modifier) { @@ -40,9 +41,9 @@ Tinytest.addAsync('update - local collection documents should have extra propert }) Tinytest.addAsync('update - local collection should fire after-update hook', function (test, next) { - var collection = new Collection(null) - var c = 0 - var n = function () { if (++c === 2) { next() } } + const collection = new Mongo.Collection(null) + let c = 0 + const n = () => { if (++c === 2) { next() } } function start () { collection.after.update(function (userId, doc, fieldNames, modifier) { @@ -59,7 +60,7 @@ Tinytest.addAsync('update - local collection should fire after-update hook', fun test.equal(fieldNames[0], 'update_value') test.equal(doc.update_value, true) - test.equal(_.has(this.previous || {}, 'update_value'), false) + test.equal((this.previous || {}).hasOwnProperty('update_value'), false) n() }) @@ -78,7 +79,7 @@ Tinytest.addAsync('update - local collection should fire after-update hook', fun }) Tinytest.addAsync('update - local collection should fire before-update hook without options in update and still fire end-callback', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) function start () { collection.before.update(function (userId, doc, fieldNames, modifier) { @@ -98,9 +99,9 @@ Tinytest.addAsync('update - local collection should fire before-update hook with }) Tinytest.addAsync('update - local collection should fire after-update hook without options in update and still fire end-callback', function (test, next) { - var collection = new Collection(null) - var c = 0 - var n = function () { if (++c === 2) { next() } } + const collection = new Mongo.Collection(null) + let c = 0 + const n = () => { if (++c === 2) { next() } } function start () { collection.after.update(function (userId, doc, fieldNames, modifier) { diff --git a/tests/upsert.js b/tst/upsert.js similarity index 90% rename from tests/upsert.js rename to tst/upsert.js index e06f718..bde1dc4 100644 --- a/tests/upsert.js +++ b/tst/upsert.js @@ -1,10 +1,11 @@ -/* global Tinytest Meteor Mongo InsecureLogin */ - -var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Tinytest } from 'meteor/tinytest'; +import { InsecureLogin } from './insecure_login'; Tinytest.addAsync('upsert - hooks should all fire the appropriate number of times', function (test, next) { - var collection = new Collection(null) - var counts = { + const collection = new Mongo.Collection(null) + const counts = { before: { insert: 0, update: 0, @@ -53,8 +54,8 @@ Tinytest.addAsync('upsert - hooks should all fire the appropriate number of time if (Meteor.isServer) { Tinytest.add('upsert - hooks should all fire the appropriate number of times in a synchronous environment', function (test) { - var collection = new Collection(null) - var counts = { + const collection = new Mongo.Collection(null) + const counts = { before: { insert: 0, update: 0, @@ -80,7 +81,7 @@ if (Meteor.isServer) { collection.after.upsert(function () { counts.after.upsert++ }) collection.remove({test: true}) - var obj = collection.upsert({test: true}, {test: true, step: 'insert'}) + const obj = collection.upsert({test: true}, {test: true, step: 'insert'}) collection.upsert(obj.insertedId, {test: true, step: 'update'}) test.equal(counts.before.insert, 0, 'before.insert should be 0') @@ -95,7 +96,7 @@ if (Meteor.isServer) { } Tinytest.addAsync('issue #156 - upsert after.insert should have a correct doc using $set', function (test, next) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.after.insert(function (userId, doc) { test.isNotUndefined(doc, 'doc should not be undefined') @@ -111,7 +112,7 @@ Tinytest.addAsync('issue #156 - upsert after.insert should have a correct doc us if (Meteor.isServer) { Tinytest.add('issue #156 - upsert after.insert should have a correct doc using $set in synchronous environment', function (test) { - var collection = new Collection(null) + const collection = new Mongo.Collection(null) collection.after.insert(function (userId, doc) { test.isNotUndefined(doc, 'doc should not be undefined') diff --git a/update.js b/update.js index 3094ed1..9c91cb6 100644 --- a/update.js +++ b/update.js @@ -1,44 +1,41 @@ -/* global CollectionHooks _ EJSON */ +import { EJSON } from 'meteor/ejson'; +import { CollectionHooks } from './collection-hooks'; -CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var callback = args[args.length - 1] - var async = typeof callback === 'function' - var docs - var docIds - var fields - var abort - var prev = {} +const isEmpty = a => !Array.isArray(a) || !a.length; - // args[0] : selector - // args[1] : mutator - // args[2] : options (optional) - // args[3] : callback +CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspects, getTransform, args, suppressAspects) { - if (typeof args[2] === 'function') { - callback = args[2] - args[2] = {} + const ctx = {context: this, _super, args} + let [selector, mutator, options, callback] = args; + if (typeof options === 'function') { + callback = options; + options = {}; } + const async = typeof callback === 'function' + let docs + let docIds + let fields + let abort + const prev = {} if (!suppressAspects) { try { - if (!_.isEmpty(aspects.before) || !_.isEmpty(aspects.after)) { - fields = CollectionHooks.getFields(args[1]) - docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() - docIds = docs.map(function (doc) { return doc._id }) + if (!isEmpty(aspects.before) || !isEmpty(aspects.after)) { + fields = CollectionHooks.getFields(mutator) + docs = CollectionHooks.getDocs.call(this, instance, selector, options).fetch() + docIds = docs.map(doc => doc._id) } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspects.after)) { - prev.mutator = EJSON.clone(args[1]) - prev.options = EJSON.clone(args[2]) + if (!isEmpty(aspects.after)) { + prev.mutator = EJSON.clone(mutator) + prev.options = EJSON.clone(options) if ( - aspects.after.some(function (o) { return o.options.fetchPrevious !== false }) && + aspects.after.some(o => o.options.fetchPrevious !== false) && CollectionHooks.extendOptions(instance.hookOptions, {}, 'after', 'update').fetchPrevious !== false ) { prev.docs = {} - docs.forEach(function (doc) { + docs.forEach((doc) => { prev.docs[doc._id] = EJSON.clone(doc) }) } @@ -47,32 +44,30 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec // before aspects.before.forEach(function (o) { docs.forEach(function (doc) { - var r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc, fields, args[1], args[2]) + const r = o.aspect.call({transform: getTransform(doc), ...ctx}, userId, doc, fields, mutator, options) if (r === false) abort = true }) }) if (abort) return 0 } catch (e) { - if (async) return callback.call(self, e) + if (async) return callback.call(this, e) throw e } } - function after (affected, err) { - if (!suppressAspects) { - if (!_.isEmpty(aspects.after)) { - var fields = CollectionHooks.getFields(args[1]) - var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() - } + const after = (affected, err) => { + if (!suppressAspects && !isEmpty(aspects.after)) { + const fields = CollectionHooks.getFields(mutator) + const docs = CollectionHooks.getDocs.call(this, instance, {_id: {$in: docIds}}, options).fetch() - aspects.after.forEach(function (o) { - docs.forEach(function (doc) { + aspects.after.forEach((o) => { + docs.forEach((doc) => { o.aspect.call({ transform: getTransform(doc), previous: prev.docs && prev.docs[doc._id], - affected: affected, - err: err, + affected, + err, ...ctx }, userId, doc, fields, prev.mutator, prev.options) }) @@ -81,13 +76,13 @@ CollectionHooks.defineAdvice('update', function (userId, _super, instance, aspec } if (async) { - args[args.length - 1] = function (err, affected) { + const wrappedCallback = function (err, affected, ...args) { after(affected, err) - return callback.apply(this, arguments) + return callback.call(this, err, affected, ...args) } - return _super.apply(this, args) + return _super.call(this, selector, mutator, options, wrappedCallback) } else { - var affected = _super.apply(self, args) + const affected = _super.call(this, selector, mutator, options, callback) after(affected) return affected } diff --git a/upsert.js b/upsert.js index d6667ae..85bc226 100644 --- a/upsert.js +++ b/upsert.js @@ -1,87 +1,81 @@ -/* global CollectionHooks _ EJSON */ -CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspectGroup, getTransform, args, suppressAspects) { - var self = this - var ctx = {context: self, _super: _super, args: args} - var callback = args[args.length - 1] - var async = typeof callback === 'function' - var docs - var docIds - var abort - var prev = {} +import { EJSON } from 'meteor/ejson'; +import { CollectionHooks } from './collection-hooks'; - // args[0] : selector - // args[1] : mutator - // args[2] : options (optional) - // args[3] : callback +const isEmpty = a => !Array.isArray(a) || !a.length; - if (typeof args[2] === 'function') { - callback = args[2] - args[2] = {} +CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspectGroup, getTransform, args, suppressAspects) { + const ctx = {context: this, _super, args} + let [selector, mutator, options, callback] = args; + if (typeof options === 'function') { + callback = options; + options = {}; } + const async = typeof callback === 'function' + let docs + let docIds + let abort + const prev = {} + if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.upsert.before)) { - docs = CollectionHooks.getDocs.call(self, instance, args[0], args[2]).fetch() - docIds = docs.map(function (doc) { return doc._id }) + if (!isEmpty(aspectGroup.upsert.before)) { + docs = CollectionHooks.getDocs.call(this, instance, selector, options).fetch() + docIds = docs.map(doc => doc._id) } // copy originals for convenience for the 'after' pointcut - if (!_.isEmpty(aspectGroup.update.after)) { - if (aspectGroup.update.after.some(function (o) { return o.options.fetchPrevious !== false }) && + if (!isEmpty(aspectGroup.update.after)) { + if (aspectGroup.update.after.some(o => o.options.fetchPrevious !== false) && CollectionHooks.extendOptions(instance.hookOptions, {}, 'after', 'update').fetchPrevious !== false) { - prev.mutator = EJSON.clone(args[1]) - prev.options = EJSON.clone(args[2]) + prev.mutator = EJSON.clone(mutator) + prev.options = EJSON.clone(options) prev.docs = {} - docs.forEach(function (doc) { + docs.forEach((doc) => { prev.docs[doc._id] = EJSON.clone(doc) }) } } // before - aspectGroup.upsert.before.forEach(function (o) { - var r = o.aspect.call(ctx, userId, args[0], args[1], args[2]) - if (r === false) abort = true + aspectGroup.upsert.before.forEach((o) => { + const r = o.aspect.call(ctx, userId, selector, mutator, options) + if (r === false) abortMongo.Collection = true }) if (abort) return { numberAffected: 0 } } - function afterUpdate (affected, err) { - if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.update.after)) { - var fields = CollectionHooks.getFields(args[1]) - var docs = CollectionHooks.getDocs.call(self, instance, {_id: {$in: docIds}}, args[2]).fetch() - } - - aspectGroup.update.after.forEach(function (o) { - docs.forEach(function (doc) { + const afterUpdate = (affected, err) => { + if (!suppressAspects && !isEmpty(aspectGroup.update.after)) { + const fields = CollectionHooks.getFields(selector) + const docs = CollectionHooks.getDocs.call(this, instance, {_id: {$in: docIds}}, options).fetch() + + aspectGroup.update.after.forEach((o) => { + docs.forEach((doc) => { o.aspect.call({ transform: getTransform(doc), previous: prev.docs && prev.docs[doc._id], - affected: affected, - err: err, + affected, + err, ...ctx}, userId, doc, fields, prev.mutator, prev.options) }) }) } } - function afterInsert (id, err) { - if (!suppressAspects) { - if (!_.isEmpty(aspectGroup.insert.after)) { - var doc = CollectionHooks.getDocs.call(self, instance, {_id: id}, args[0], {}).fetch()[0] // 3rd argument passes empty object which causes magic logic to imply limit:1 - var lctx = {transform: getTransform(doc), _id: id, err: err, ...ctx} - } - - aspectGroup.insert.after.forEach(function (o) { + const afterInsert = (_id, err) => { + if (!suppressAspects && !isEmpty(aspectGroup.insert.after)) { + const doc = CollectionHooks.getDocs.call(this, instance, {_id}, selector, {}).fetch()[0] // 3rd argument passes empty object which causes magic logic to imply limit:1 + const lctx = {transform: getTransform(doc), _id, err, ...ctx} + + aspectGroup.insert.after.forEach((o) => { o.aspect.call(lctx, userId, doc) }) } } if (async) { - args[args.length - 1] = function (err, ret) { + const wrappedCallback = function (err, ret) { if (err || (ret && ret.insertedId)) { // Send any errors to afterInsert afterInsert(ret.insertedId, err) @@ -94,13 +88,9 @@ CollectionHooks.defineAdvice('upsert', function (userId, _super, instance, aspec }) } - return CollectionHooks.directOp(function () { - return _super.apply(self, args) - }) + return CollectionHooks.directOp(() => _super.call(this, selector, mutator, options,wrappedCallback)); } else { - var ret = CollectionHooks.directOp(function () { - return _super.apply(self, args) - }) + const ret = CollectionHooks.directOp(() => _super.call(this, selector, mutator, options, callback)); if (ret && ret.insertedId) { afterInsert(ret.insertedId) diff --git a/users-compat.js b/users-compat.js index b2f6253..3449b79 100644 --- a/users-compat.js +++ b/users-compat.js @@ -1,10 +1,11 @@ -/* global CollectionHooks Meteor Mongo */ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { CollectionHooks } from './collection-hooks'; if (Meteor.users) { // If Meteor.users has been instantiated, attempt to re-assign its prototype: CollectionHooks.reassignPrototype(Meteor.users) // Next, give it the hook aspects: - var Collection = typeof Mongo !== 'undefined' && typeof Mongo.Collection !== 'undefined' ? Mongo.Collection : Meteor.Collection - CollectionHooks.extendCollectionInstance(Meteor.users, Collection) + CollectionHooks.extendCollectionInstance(Meteor.users, Mongo.Collection) } From a931457fe860b6603d01a35a4db1f7fbbedefa30 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Wed, 9 Oct 2019 13:07:30 +0900 Subject: [PATCH 9/9] Update history --- History.md | 4 ++++ package-lock.json | 16 +++++++++++++--- package.json | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index b901510..6b6cb12 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +## v1.0.0 +* Modernization of the package +* BREAKING CHANGE: Minimum required Meteor version is now 1.7.0.5 + ## v0.9.1 * Fixed selector not being able to be modified when String or MongoID is used * Add `npm prune --production` to publication script to prevent addition of dev only packages to the bundle. Fixes issue [#246](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/issues/246) diff --git a/package-lock.json b/package-lock.json index db43cdf..8fd26cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -473,6 +473,16 @@ "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", "dev": true }, + "eslint-import-resolver-meteor": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-meteor/-/eslint-import-resolver-meteor-0.4.0.tgz", + "integrity": "sha1-yGhjhAghIIz4EzxczlGQnCamFWk=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "resolve": "^1.1.6" + } + }, "eslint-import-resolver-node": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", @@ -986,9 +996,9 @@ } }, "hosted-git-info": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, "http-signature": { diff --git a/package.json b/package.json index 053650b..a09e73c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "devDependencies": { "eslint": "^6.5.1", "eslint-config-standard": "^14.1.0", + "eslint-import-resolver-meteor": "^0.4.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-node": "^10.0.0", "eslint-plugin-promise": "^4.2.1",