From 53b3e59b3e61ff2e83a5d0cf5510a7ed336f0185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Miernik?= Date: Mon, 10 Jul 2017 20:03:34 +0200 Subject: [PATCH] Removed underscore from minimongo. --- packages/minimongo/helpers.js | 6 +- packages/minimongo/minimongo.js | 58 +++++++-------- packages/minimongo/minimongo_tests.js | 87 +++++++++++----------- packages/minimongo/modify.js | 24 +++--- packages/minimongo/objectid.js | 12 +-- packages/minimongo/observe.js | 6 +- packages/minimongo/package.js | 2 - packages/minimongo/projection.js | 54 +++++++------- packages/minimongo/selector.js | 68 ++++++++--------- packages/minimongo/selector_modifier.js | 52 +++++++------ packages/minimongo/selector_projection.js | 16 ++-- packages/minimongo/sort.js | 57 +++++++------- packages/minimongo/validation.js | 4 +- packages/minimongo/wrap_transform.js | 4 +- packages/minimongo/wrap_transform_tests.js | 4 +- 15 files changed, 234 insertions(+), 220 deletions(-) diff --git a/packages/minimongo/helpers.js b/packages/minimongo/helpers.js index a21c1dc49c..aa76b1f57b 100644 --- a/packages/minimongo/helpers.js +++ b/packages/minimongo/helpers.js @@ -2,7 +2,7 @@ // arrays. // XXX maybe this should be EJSON.isArray isArray = function (x) { - return _.isArray(x) && !EJSON.isBinary(x); + return Array.isArray(x) && !EJSON.isBinary(x); }; // XXX maybe this should be EJSON.isObject, though EJSON doesn't know about @@ -24,7 +24,7 @@ isOperatorObject = function (valueSelector, inconsistentOK) { return false; var theseAreOperators = undefined; - _.each(valueSelector, function (value, selKey) { + Object.keys(valueSelector).forEach(function (selKey) { var thisIsOperator = selKey.substr(0, 1) === '$'; if (theseAreOperators === undefined) { theseAreOperators = thisIsOperator; @@ -42,4 +42,4 @@ isOperatorObject = function (valueSelector, inconsistentOK) { // string can be converted to integer isNumericKey = function (s) { return /^[0-9]+$/.test(s); -}; \ No newline at end of file +}; diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index b97663a8a7..d02bcd40a3 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -170,7 +170,7 @@ LocalCollection.Cursor.prototype.forEach = function (callback, thisArg) { movedBefore: true}); } - _.each(objects, function (elt, i) { + objects.forEach(function (elt, i) { // This doubles as a clone operation. elt = self._projectionFn(elt); @@ -298,7 +298,7 @@ LocalCollection.ObserveHandle = function () {}; // XXX maybe callbacks should take a list of objects, to expose transactions? // XXX maybe support field limiting (to limit what you're notified on) -_.extend(LocalCollection.Cursor.prototype, { +Object.assign(LocalCollection.Cursor.prototype, { /** * @summary Watch a query. Receive callbacks as the result set changes. * @locus Anywhere @@ -388,11 +388,9 @@ _.extend(LocalCollection.Cursor.prototype, { } if (!options._suppress_initial && !self.collection.paused) { - // XXX unify ordered and unordered interface - var each = ordered - ? _.bind(_.each, null, query.results) - : _.bind(query.results.forEach, query.results); - each(function (doc) { + var results = query.results._map || query.results; + Object.keys(results).forEach(function (key) { + var doc = results[key]; var fields = EJSON.clone(doc); delete fields._id; @@ -403,7 +401,7 @@ _.extend(LocalCollection.Cursor.prototype, { } var handle = new LocalCollection.ObserveHandle; - _.extend(handle, { + Object.assign(handle, { collection: self.collection, stop: function () { if (self.reactive) @@ -525,17 +523,16 @@ LocalCollection.Cursor.prototype._depend = function (changers, _allow_unordered) if (Tracker.active) { var v = new Tracker.Dependency; v.depend(); - var notifyChange = _.bind(v.changed, v); + var notifyChange = v.changed.bind(v); var options = { _suppress_initial: true, _allow_unordered: _allow_unordered }; - _.each(['added', 'changed', 'removed', 'addedBefore', 'movedBefore'], - function (fnName) { - if (changers[fnName]) - options[fnName] = notifyChange; - }); + ['added', 'changed', 'removed', 'addedBefore', 'movedBefore'].forEach(function (fnName) { + if (changers[fnName]) + options[fnName] = notifyChange; + }); // observeChanges will stop() when this computation is invalidated self.observeChanges(options); @@ -550,7 +547,7 @@ LocalCollection.prototype.insert = function (doc, callback) { assertHasValidFieldNames(doc); - if (!_.has(doc, '_id')) { + if (!doc.hasOwnProperty('_id')) { // if you really want to use ObjectIDs, set this global. // Mongo.Collection specifies its own ids and does not use this code. doc._id = LocalCollection._useOID ? new MongoID.ObjectID() @@ -580,7 +577,7 @@ LocalCollection.prototype.insert = function (doc, callback) { } } - _.each(queriesToRecompute, function (qid) { + queriesToRecompute.forEach(function (qid) { if (self.queries[qid]) self._recomputeResults(self.queries[qid]); }); @@ -626,7 +623,8 @@ LocalCollection.prototype.remove = function (selector, callback) { if (self.paused && !self._savedOriginals && EJSON.equals(selector, {})) { var result = self._docs.size(); self._docs.clear(); - _.each(self.queries, function (query) { + Object.keys(self.queries).forEach(function (qid) { + var query = self.queries[qid]; if (query.ordered) { query.results = []; } else { @@ -653,7 +651,8 @@ LocalCollection.prototype.remove = function (selector, callback) { for (var i = 0; i < remove.length; i++) { var removeId = remove[i]; var removeDoc = self._docs.get(removeId); - _.each(self.queries, function (query, qid) { + Object.keys(self.queries).forEach(function (qid) { + var query = self.queries[qid]; if (query.dirty) return; if (query.matcher.documentMatches(removeDoc).result) { @@ -668,14 +667,14 @@ LocalCollection.prototype.remove = function (selector, callback) { } // run live query callbacks _after_ we've removed the documents. - _.each(queryRemove, function (remove) { + queryRemove.forEach(function (remove) { var query = self.queries[remove.qid]; if (query) { query.distances && query.distances.remove(remove.doc._id); LocalCollection._removeFromResults(query, remove.doc); } }); - _.each(queriesToRecompute, function (qid) { + queriesToRecompute.forEach(function (qid) { var query = self.queries[qid]; if (query) self._recomputeResults(query); @@ -711,7 +710,8 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { var docMap = new LocalCollection._IdMap; var idsMatchedBySelector = LocalCollection._idsMatchedBySelector(selector); - _.each(self.queries, function (query, qid) { + Object.keys(self.queries).forEach(function (qid) { + var query = self.queries[qid]; if ((query.cursor.skip || query.cursor.limit) && ! self.paused) { // Catch the case of a reactive `count()` on a cursor with skip // or limit, which registers an unordered observe. This is a @@ -737,7 +737,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { } else { var docToMemoize; - if (idsMatchedBySelector && !_.any(idsMatchedBySelector, function(id) { + if (idsMatchedBySelector && !idsMatchedBySelector.some(function(id) { return EJSON.equals(id, doc._id); })) { docToMemoize = doc; @@ -770,7 +770,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { return true; }); - _.each(recomputeQids, function (dummy, qid) { + Object.keys(recomputeQids).forEach(function (qid) { var query = self.queries[qid]; if (query) self._recomputeResults(query, qidToOriginalResults[qid]); @@ -783,8 +783,8 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { var insertedId; if (updateCount === 0 && options.upsert) { - let selectorModifier = LocalCollection._selectorIsId(selector) - ? { _id: selector } + let selectorModifier = LocalCollection._selectorIsId(selector) + ? { _id: selector } : selector; selectorModifier = LocalCollection._removeDollarOperators(selectorModifier); @@ -795,7 +795,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { delete selectorModifier._id; } - // This double _modify call is made to help work around an issue where collection + // This double _modify call is made to help work around an issue where collection // upserts won't work properly, with nested properties (see issue #8631). LocalCollection._modify(newDoc, {$set: selectorModifier}); LocalCollection._modify(newDoc, mod, {isInsert: true}); @@ -836,7 +836,7 @@ LocalCollection.prototype.upsert = function (selector, mod, options, callback) { callback = options; options = {}; } - return self.update(selector, mod, _.extend({}, options, { + return self.update(selector, mod, Object.assign({}, options, { upsert: true, _returnObject: true }), callback); @@ -945,7 +945,7 @@ LocalCollection._updateInResults = function (query, doc, old_doc) { projectionFn(doc), projectionFn(old_doc)); if (!query.ordered) { - if (!_.isEmpty(changedFields)) { + if (Object.keys(changedFields).length) { query.changed(doc._id, changedFields); query.results.set(doc._id, doc); } @@ -954,7 +954,7 @@ LocalCollection._updateInResults = function (query, doc, old_doc) { var orig_idx = LocalCollection._findInOrderedResults(query, doc); - if (!_.isEmpty(changedFields)) + if (Object.keys(changedFields).length) query.changed(doc._id, changedFields); if (!query.sorter) return; diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 8e85819d29..c3081b22a1 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -241,7 +241,11 @@ Tinytest.add("minimongo - transform", function (test) { }); // transformed documents get _id field transplanted if not present - var transformWithoutId = function (doc) { return _.omit(doc, '_id'); }; + var transformWithoutId = function (doc) { + var docWithoutId = Object.assign({}, doc); + delete docWithoutId._id; + return docWithoutId; + }; test.equal(c.findOne({}, {transform: transformWithoutId})._id, c.findOne()._id); }); @@ -333,8 +337,8 @@ Tinytest.add("minimongo - selector_compiler", function (test) { } }; - var match = _.bind(matches, null, true); - var nomatch = _.bind(matches, null, false); + var match = matches.bind(null, true); + var nomatch = matches.bind(null, false); // XXX blog post about what I learned while writing these tests (weird // mongo edge cases) @@ -513,14 +517,14 @@ Tinytest.add("minimongo - selector_compiler", function (test) { nomatch({a: {$mod: [10, 1]}}, {a: 12}); match({a: {$mod: [10, 1]}}, {a: [10, 11, 12]}); nomatch({a: {$mod: [10, 1]}}, {a: [10, 12]}); - _.each([ + [ 5, [10], [10, 1, 2], "foo", {bar: 1}, [] - ], function (badMod) { + ].forEach(function (badMod) { test.throws(function () { match({a: {$mod: badMod}}, {a: 11}); }); @@ -866,9 +870,9 @@ Tinytest.add("minimongo - selector_compiler", function (test) { nomatch({a: {$bitsAllSet: 1}}, {a: ['a', 'b']}) nomatch({a: {$bitsAllSet: 1}}, {a: {foo: 'bar'}}) nomatch({a: {$bitsAllSet: 1}}, {a: 1.2}) - nomatch({a: {$bitsAllSet: 1}}, {a: "1"}) + nomatch({a: {$bitsAllSet: 1}}, {a: "1"}); - _.each([ + [ false, NaN, Infinity, @@ -879,7 +883,7 @@ Tinytest.add("minimongo - selector_compiler", function (test) { 1.2, "1", [0, -1] - ], function (badValue) { + ].forEach(function (badValue) { test.throws(function () { match({a: {$bitsAllSet: badValue}}, {a: 42}); }); @@ -1441,10 +1445,10 @@ Tinytest.add("minimongo - projection_compiler", function (test) { var testProjection = function (projection, tests) { var projection_f = LocalCollection._compileProjection(projection); var equalNonStrict = function (a, b, desc) { - test.isTrue(_.isEqual(a, b), desc); + test.isTrue(EJSON.equals(a, b), desc); }; - _.each(tests, function (testCase) { + tests.forEach(function (testCase) { equalNonStrict(projection_f(testCase[0]), testCase[1], testCase[2]); }); }; @@ -1570,7 +1574,7 @@ Tinytest.add("minimongo - projection_compiler", function (test) { Tinytest.add("minimongo - fetch with fields", function (test) { var c = new LocalCollection(); - _.times(30, function (i) { + Array.from({length: 30}, function (_, i) { c.insert({ something: Random.id(), anything: { @@ -1588,14 +1592,14 @@ Tinytest.add("minimongo - fetch with fields", function (test) { 'anything.foo': 1 } }).fetch(); - test.isTrue(_.all(fetchResults, function (x) { + test.isTrue(fetchResults.every(function (x) { return x && x.something && x.anything && x.anything.foo && x.anything.foo === "bar" && - !_.has(x, 'nothing') && - !_.has(x.anything, 'cool'); + !x.hasOwnProperty('nothing') && + !x.anything.hasOwnProperty('cool'); })); // Test with a selector, even field used in the selector is excluded in the @@ -1606,13 +1610,13 @@ Tinytest.add("minimongo - fetch with fields", function (test) { fields: { nothing: 0 } }).fetch(); - test.isTrue(_.all(fetchResults, function (x) { + test.isTrue(fetchResults.every(function (x) { return x && x.something && x.anything && x.anything.foo === "bar" && x.anything.cool === "hot" && - !_.has(x, 'nothing') && + !x.hasOwnProperty('nothing') && x.i && x.i >= 5; })); @@ -1634,13 +1638,13 @@ Tinytest.add("minimongo - fetch with fields", function (test) { } }).fetch(); - test.isTrue(_.all(fetchResults, function (x) { + test.isTrue(fetchResults.every(function (x) { return x && x.something && x.i >= 10 && x.i < 20; })); - _.each(fetchResults, function (x, i, arr) { + fetchResults.forEach(function (x, i, arr) { if (!i) return; test.isTrue(x.i === arr[i-1].i + 1); }); @@ -1685,7 +1689,7 @@ Tinytest.add("minimongo - fetch with projection, subarrays", function (test) { }); var equalNonStrict = function (a, b, desc) { - test.isTrue(_.isEqual(a, b), desc); + test.isTrue(EJSON.equals(a, b), desc); }; var testForProjection = function (projection, expected) { @@ -1796,7 +1800,7 @@ Tinytest.add("minimongo - observe ordered with projection", function (test) { handle.stop(); // test _suppress_initial - handle = c.find({}, {sort: {a: -1}, fields: { a: 1 }}).observe(_.extend(cbs, {_suppress_initial: true})); + handle = c.find({}, {sort: {a: -1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_suppress_initial: true})); test.equal(operations.shift(), undefined); c.insert({a:100, b: { foo: "bar" }}); test.equal(operations.shift(), ['added', {a:100}, 0, idA2]); @@ -1826,7 +1830,7 @@ Tinytest.add("minimongo - observe ordered with projection", function (test) { // test _no_indices c.remove({}); - handle = c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(_.extend(cbs, {_no_indices: true})); + handle = c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_no_indices: true})); c.insert({_id: 'foo', a:1, zoo: "crazy"}); test.equal(operations.shift(), ['added', {a:1}, -1, null]); c.update({a:1}, {$set: {a: 2, foobar: "player"}}); @@ -1875,7 +1879,7 @@ Tinytest.add("minimongo - ordering", function (test) { // document ordering under a sort specification var verify = function (sorts, docs) { - _.each(_.isArray(sorts) ? sorts : [sorts], function (sort) { + (Array.isArray(sorts) ? sorts : [sorts]).forEach(function (sort) { var sorter = new Minimongo.Sorter(sort); assert_ordering(test, sorter.getComparator(), docs); }); @@ -2050,25 +2054,25 @@ Tinytest.add("minimongo - subkey sort", function (test) { c.insert({a: {b: 1}}); c.insert({a: {b: 3}}); test.equal( - _.pluck(c.find({}, {sort: {'a.b': -1}}).fetch(), 'a'), + c.find({}, {sort: {'a.b': -1}}).fetch().map(function (doc) { return doc.a; }), [{b: 3}, {b: 2}, {b: 1}]); // isn't an object c.insert({a: 1}); test.equal( - _.pluck(c.find({}, {sort: {'a.b': 1}}).fetch(), 'a'), + c.find({}, {sort: {'a.b': 1}}).fetch().map(function (doc) { return doc.a; }), [1, {b: 1}, {b: 2}, {b: 3}]); // complex object c.insert({a: {b: {c: 1}}}); test.equal( - _.pluck(c.find({}, {sort: {'a.b': -1}}).fetch(), 'a'), + c.find({}, {sort: {'a.b': -1}}).fetch().map(function (doc) { return doc.a; }), [{b: {c: 1}}, {b: 3}, {b: 2}, {b: 1}, 1]); // no such top level prop c.insert({c: 1}); test.equal( - _.pluck(c.find({}, {sort: {'a.b': -1}}).fetch(), 'a'), + c.find({}, {sort: {'a.b': -1}}).fetch().map(function (doc) { return doc.a; }), [{b: {c: 1}}, {b: 3}, {b: 2}, {b: 1}, 1, undefined]); // no such mid level prop. just test that it doesn't throw. @@ -2099,11 +2103,11 @@ Tinytest.add("minimongo - array sort", function (test) { var testCursorMatchesField = function (cursor, field) { var fieldValues = []; c.find().forEach(function (doc) { - if (_.has(doc, field)) + if (doc.hasOwnProperty(field)) fieldValues.push(doc[field]); }); - test.equal(_.pluck(cursor.fetch(), field), - _.range(_.max(fieldValues) + 1)); + test.equal(cursor.fetch().map(function (doc) { return doc[field]; }), + Array.from({length: Math.max.apply(null, fieldValues) + 1}, function (_, i) { return i; })); }; testCursorMatchesField(c.find({}, {sort: {'a.x': 1}}), 'up'); @@ -2115,7 +2119,7 @@ Tinytest.add("minimongo - array sort", function (test) { Tinytest.add("minimongo - sort keys", function (test) { var keyListToObject = function (keyList) { var obj = {}; - _.each(keyList, function (key) { + keyList.forEach(function (key) { obj[EJSON.stringify(key)] = true; }); return obj; @@ -2961,7 +2965,7 @@ Tinytest.add("minimongo - observe ordered", function (test) { handle.stop(); // test _suppress_initial - handle = c.find({}, {sort: {a: -1}}).observe(_.extend({ + handle = c.find({}, {sort: {a: -1}}).observe(Object.assign({ _suppress_initial: true}, cbs)); test.equal(operations.shift(), undefined); c.insert({a:100}); @@ -3007,7 +3011,7 @@ Tinytest.add("minimongo - observe ordered", function (test) { // test _no_indices c.remove({}); - handle = c.find({}, {sort: {a: 1}}).observe(_.extend(cbs, {_no_indices: true})); + handle = c.find({}, {sort: {a: 1}}).observe(Object.assign(cbs, {_no_indices: true})); c.insert({_id: 'foo', a:1}); test.equal(operations.shift(), ['added', {a:1}, -1, null]); c.update({a:1}, {$set: {a: 2}}); @@ -3027,14 +3031,14 @@ Tinytest.add("minimongo - observe ordered", function (test) { handle.stop(); }); -_.each([true, false], function (ordered) { +[true, false].forEach(function (ordered) { Tinytest.add("minimongo - observe ordered: " + ordered, function (test) { var c = new LocalCollection(); var ev = ""; var makecb = function (tag) { var ret = {}; - _.each(["added", "changed", "removed"], function (fn) { + ["added", "changed", "removed"].forEach(function (fn) { var fnName = ordered ? fn + "At" : fn; ret[fnName] = function (doc) { ev = (ev + fn.substr(0, 1) + tag + doc._id + "_"); @@ -3112,8 +3116,8 @@ Tinytest.add("minimongo - saveOriginals", function (test) { // Verify the originals. var originals = c.retrieveOriginals(); var affected = ['bar', 'baz', 'quux', 'whoa', 'hooray']; - test.equal(originals.size(), _.size(affected)); - _.each(affected, function (id) { + test.equal(originals.size(), affected.length); + affected.forEach(function (id) { test.isTrue(originals.has(id)); }); test.equal(originals.get('bar'), {_id: 'bar', x: 'updateme'}); @@ -3378,7 +3382,7 @@ Tinytest.add("minimongo - $near operator tests", function (test) { test.equal(coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 30 } }).count(), 3); test.equal(coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 4 } }).count(), 1); var points = coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 6 } }).fetch(); - _.each(points, function (point, i, points) { + points.forEach(function (point, i, points) { test.isTrue(!i || distance([0, 0], point.rest.loc) >= distance([0, 0], points[i - 1].rest.loc)); }); @@ -3397,7 +3401,7 @@ Tinytest.add("minimongo - $near operator tests", function (test) { { "category" : "OTHER OFFENSES", "descript" : "POSSESSION OF BURGLARY TOOLS", "address" : "900 Block of MINNA ST", "location" : { "type" : "Point", "coordinates" : [ -122.415386041221, 37.7747879734156 ] } } ]; - _.each(data, function (x, i) { coll.insert(_.extend(x, { x: i })); }); + data.forEach(function (x, i) { coll.insert(Object.assign(x, { x: i })); }); var close15 = coll.find({ location: { $near: { $geometry: { type: "Point", @@ -3478,8 +3482,7 @@ Tinytest.add("minimongo - $near operator tests", function (test) { a: {b: [5, 5]}}); var testNear = function (near, md, expected) { test.equal( - _.pluck( - coll.find({'a.b': {$near: near, $maxDistance: md}}).fetch(), '_id'), + coll.find({'a.b': {$near: near, $maxDistance: md}}).fetch().map(function (doc) { return doc._id }), expected); }; testNear([149, 149], 4, ['x']); @@ -3492,10 +3495,10 @@ Tinytest.add("minimongo - $near operator tests", function (test) { // issue #3599 // Ensure that distance is not used as a tie-breaker for sort. test.equal( - _.pluck(coll.find({'a.b': {$near: [1, 1]}}, {sort: {k: 1}}).fetch(), '_id'), + coll.find({'a.b': {$near: [1, 1]}}, {sort: {k: 1}}).fetch().map(function (doc) { return doc._id; }), ['x', 'y']); test.equal( - _.pluck(coll.find({'a.b': {$near: [5, 5]}}, {sort: {k: 1}}).fetch(), '_id'), + coll.find({'a.b': {$near: [5, 5]}}, {sort: {k: 1}}).fetch().map(function (doc) { return doc._id; }), ['x', 'y']); var operations = []; diff --git a/packages/minimongo/modify.js b/packages/minimongo/modify.js index e0df49a274..5c5e025f34 100644 --- a/packages/minimongo/modify.js +++ b/packages/minimongo/modify.js @@ -35,14 +35,16 @@ LocalCollection._modify = function (doc, mod, options) { // apply modifiers to the doc. newDoc = EJSON.clone(doc); - _.each(mod, function (operand, op) { + Object.keys(mod).forEach(function (op) { + var operand = mod[op]; var modFunc = MODIFIERS[op]; // Treat $setOnInsert as $set if this is an insert. if (options.isInsert && op === '$setOnInsert') modFunc = MODIFIERS['$set']; if (!modFunc) throw MinimongoError("Invalid modifier specified " + op); - _.each(operand, function (arg, keypath) { + Object.keys(operand).forEach(function (keypath) { + var arg = operand[keypath]; if (keypath === '') { throw MinimongoError("An empty update path is not valid."); } @@ -53,13 +55,13 @@ LocalCollection._modify = function (doc, mod, options) { var keyparts = keypath.split('.'); - if (! _.all(keyparts, _.identity)) { + if (!keyparts.every(Boolean)) { throw MinimongoError( "The update path '" + keypath + "' contains an empty field name, which is not allowed."); } - var noCreate = _.has(NO_CREATE_MODIFIERS, op); + var noCreate = NO_CREATE_MODIFIERS.hasOwnProperty(op); var forbidArray = (op === "$rename"); var target = findModTarget(newDoc, keyparts, { noCreate: NO_CREATE_MODIFIERS[op], @@ -73,15 +75,15 @@ LocalCollection._modify = function (doc, mod, options) { } // move new document into place. - _.each(_.keys(doc), function (k) { + Object.keys(doc).forEach(function (k) { // Note: this used to be for (var k in doc) however, this does not // work right in Opera. Deleting from a doc while iterating over it // would sometimes cause opera to skip some keys. if (k !== '_id') delete doc[k]; }); - _.each(newDoc, function (v, k) { - doc[k] = v; + Object.keys(newDoc).forEach(function (k) { + doc[k] = newDoc[k]; }); }; @@ -237,7 +239,7 @@ var MODIFIERS = { } }, $set: function (target, field, arg) { - if (!_.isObject(target)) { // not an array or an object + if (target !== Object(target)) { // not an array or an object var e = MinimongoError( "Cannot set property on non-object field", { field }); e.setPropertyError = true; @@ -343,8 +345,8 @@ var MODIFIERS = { target[field] = []; // differs from Array.slice! else if (slice < 0) target[field] = target[field].slice(slice); - else - target[field] = target[field].slice(0, slice); + else + target[field] = target[field].slice(0, slice); } }, $pushAll: function (target, field, arg) { @@ -380,7 +382,7 @@ var MODIFIERS = { throw MinimongoError( "Cannot apply $addToSet modifier to non-array", { field }); else { - _.each(values, function (value) { + values.forEach(function (value) { for (var i = 0; i < x.length; i++) if (LocalCollection._f._equal(value, x[i])) return; diff --git a/packages/minimongo/objectid.js b/packages/minimongo/objectid.js index 3acf04e502..fdf529a356 100644 --- a/packages/minimongo/objectid.js +++ b/packages/minimongo/objectid.js @@ -10,7 +10,7 @@ LocalCollection._selectorIsIdPerhapsAsObject = function (selector) { return LocalCollection._selectorIsId(selector) || (selector && typeof selector === "object" && selector._id && LocalCollection._selectorIsId(selector._id) && - _.size(selector) === 1); + Object.keys(selector).length === 1); }; // If this is a selector which explicitly constrains the match by ID to a finite @@ -26,15 +26,15 @@ LocalCollection._idsMatchedBySelector = function (selector) { return null; // Do we have an _id clause? - if (_.has(selector, '_id')) { + if (selector.hasOwnProperty('_id')) { // Is the _id clause just an ID? if (LocalCollection._selectorIsId(selector._id)) return [selector._id]; // Is the _id clause {_id: {$in: ["x", "y", "z"]}}? if (selector._id && selector._id.$in - && _.isArray(selector._id.$in) - && !_.isEmpty(selector._id.$in) - && _.all(selector._id.$in, LocalCollection._selectorIsId)) { + && Array.isArray(selector._id.$in) + && selector._id.$in.length + && selector._id.$in.every(LocalCollection._selectorIsId)) { return selector._id.$in; } return null; @@ -43,7 +43,7 @@ LocalCollection._idsMatchedBySelector = function (selector) { // If this is a top-level $and, and any of the clauses constrain their // documents, then the whole selector is constrained by any one clause's // constraint. (Well, by their intersection, but that seems unlikely.) - if (selector.$and && _.isArray(selector.$and)) { + if (selector.$and && Array.isArray(selector.$and)) { for (var i = 0; i < selector.$and.length; ++i) { var subIds = LocalCollection._idsMatchedBySelector(selector.$and[i]); if (subIds) diff --git a/packages/minimongo/observe.js b/packages/minimongo/observe.js index bbc5f9eb42..6733bf08f7 100644 --- a/packages/minimongo/observe.js +++ b/packages/minimongo/observe.js @@ -13,7 +13,7 @@ LocalCollection._CachingChangeObserver = function (options) { var orderedFromCallbacks = options.callbacks && LocalCollection._observeChangesCallbacksAreOrdered(options.callbacks); - if (_.has(options, 'ordered')) { + if (options.hasOwnProperty('ordered')) { self.ordered = options.ordered; if (options.callbacks && options.ordered !== orderedFromCallbacks) throw Error("ordered option doesn't match callbacks"); @@ -89,7 +89,7 @@ LocalCollection._observeFromObserveChanges = function (cursor, observeCallbacks) var self = this; if (suppressed || !(observeCallbacks.addedAt || observeCallbacks.added)) return; - var doc = transform(_.extend(fields, {_id: id})); + var doc = transform(Object.assign(fields, {_id: id})); if (observeCallbacks.addedAt) { var index = indices ? (before ? self.docs.indexOf(before) : self.docs.size()) : -1; @@ -149,7 +149,7 @@ LocalCollection._observeFromObserveChanges = function (cursor, observeCallbacks) observeChangesCallbacks = { added: function (id, fields) { if (!suppressed && observeCallbacks.added) { - var doc = _.extend(fields, {_id: id}); + var doc = Object.assign(fields, {_id: id}); observeCallbacks.added(transform(doc)); } }, diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index c389236fd5..17b63b9036 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -9,7 +9,6 @@ Package.onUse(function (api) { api.export('MinimongoTest', { testOnly: true }); api.export('MinimongoError', { testOnly: true }); api.use([ - 'underscore', 'ejson', 'id-map', 'ordered-dict', @@ -51,7 +50,6 @@ Package.onTest(function (api) { api.use('test-helpers', 'client'); api.use([ 'tinytest', - 'underscore', 'ejson', 'ordered-dict', 'random', diff --git a/packages/minimongo/projection.js b/packages/minimongo/projection.js index 863f01f71d..d586acbec8 100644 --- a/packages/minimongo/projection.js +++ b/packages/minimongo/projection.js @@ -8,22 +8,23 @@ LocalCollection._compileProjection = function (fields) { LocalCollection._checkSupportedProjection(fields); - var _idProjection = _.isUndefined(fields._id) ? true : fields._id; + var _idProjection = fields._id === undefined ? true : fields._id; var details = projectionDetails(fields); // returns transformed doc according to ruleTree var transform = function (doc, ruleTree) { // Special case for "sets" - if (_.isArray(doc)) - return _.map(doc, function (subdoc) { return transform(subdoc, ruleTree); }); + if (Array.isArray(doc)) + return doc.map(function (subdoc) { return transform(subdoc, ruleTree); }); var res = details.including ? {} : EJSON.clone(doc); - _.each(ruleTree, function (rule, key) { - if (!_.has(doc, key)) + Object.keys(ruleTree).forEach(function (key) { + var rule = ruleTree[key]; + if (!doc.hasOwnProperty(key)) return; - if (_.isObject(rule)) { + if (rule === Object(rule)) { // For sub-objects/subsets we branch - if (_.isObject(doc[key])) + if (doc[key] === Object(doc[key])) res[key] = transform(doc[key], rule); // Otherwise we don't even touch this subfield } else if (details.including) @@ -38,9 +39,9 @@ LocalCollection._compileProjection = function (fields) { return function (obj) { var res = transform(obj, details.tree); - if (_idProjection && _.has(obj, '_id')) + if (_idProjection && obj.hasOwnProperty('_id')) res._id = obj._id; - if (!_idProjection && _.has(res, '_id')) + if (!_idProjection && res.hasOwnProperty('_id')) delete res._id; return res; }; @@ -56,7 +57,7 @@ projectionDetails = function (fields) { // Find the non-_id keys (_id is handled specially because it is included unless // explicitly excluded). Sort the keys, so that our code to detect overlaps // like 'foo' and 'foo.bar' can assume that 'foo' comes first. - var fieldsKeys = _.keys(fields).sort(); + var fieldsKeys = Object.keys(fields).sort(); // If _id is the only field in the projection, do not remove it, since it is // required to determine if this is an exclusion or exclusion. Also keep an @@ -66,12 +67,12 @@ projectionDetails = function (fields) { // special case, since exclusive _id is always allowed. if (fieldsKeys.length > 0 && !(fieldsKeys.length === 1 && fieldsKeys[0] === '_id') && - !(_.contains(fieldsKeys, '_id') && fields['_id'])) - fieldsKeys = _.reject(fieldsKeys, function (key) { return key === '_id'; }); + !(fieldsKeys.includes('_id') && fields['_id'])) + fieldsKeys = fieldsKeys.filter(function (key) { return key !== '_id'; }); var including = null; // Unknown - _.each(fieldsKeys, function (keyPath) { + fieldsKeys.forEach(function (keyPath) { var rule = !!fields[keyPath]; if (including === null) including = rule; @@ -126,20 +127,20 @@ projectionDetails = function (fields) { // @returns - Object: tree represented as a set of nested objects pathsToTree = function (paths, newLeafFn, conflictFn, tree) { tree = tree || {}; - _.each(paths, function (keyPath) { + paths.forEach(function (keyPath) { var treePos = tree; var pathArr = keyPath.split('.'); - // use _.all just for iteration with break - var success = _.all(pathArr.slice(0, -1), function (key, idx) { - if (!_.has(treePos, key)) + // use .every just for iteration with break + var success = pathArr.slice(0, -1).every(function (key, idx) { + if (!treePos.hasOwnProperty(key)) treePos[key] = {}; - else if (!_.isObject(treePos[key])) { + else if (treePos[key] !== Object(treePos[key])) { treePos[key] = conflictFn(treePos[key], pathArr.slice(0, idx + 1).join('.'), keyPath); // break out of loop if we are failing for this path - if (!_.isObject(treePos[key])) + if (treePos[key] !== Object(treePos[key])) return false; } @@ -148,8 +149,8 @@ pathsToTree = function (paths, newLeafFn, conflictFn, tree) { }); if (success) { - var lastKey = _.last(pathArr); - if (!_.has(treePos, lastKey)) + var lastKey = pathArr[pathArr.length - 1]; + if (!treePos.hasOwnProperty(lastKey)) treePos[lastKey] = newLeafFn(keyPath); else treePos[lastKey] = conflictFn(treePos[lastKey], keyPath, keyPath); @@ -160,15 +161,16 @@ pathsToTree = function (paths, newLeafFn, conflictFn, tree) { }; LocalCollection._checkSupportedProjection = function (fields) { - if (!_.isObject(fields) || _.isArray(fields)) + if (fields !== Object(fields) || Array.isArray(fields)) throw MinimongoError("fields option must be an object"); - _.each(fields, function (val, keyPath) { - if (_.contains(keyPath.split('.'), '$')) + Object.keys(fields).forEach(function (keyPath) { + var val = fields[keyPath]; + if (keyPath.split('.').includes('$')) throw MinimongoError("Minimongo doesn't support $ operator in projections yet."); - if (typeof val === 'object' && _.intersection(['$elemMatch', '$meta', '$slice'], _.keys(val)).length > 0) + if (typeof val === 'object' && ['$elemMatch', '$meta', '$slice'].some(key => Object.keys(val).includes(key))) throw MinimongoError("Minimongo doesn't support operators in projections yet."); - if (_.indexOf([1, 0, true, false], val) === -1) + if (![1, 0, true, false].includes(val)) throw MinimongoError("Projection values should be one of 1, 0, true, or false"); }); }; diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index affe510ebf..a56af7008a 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -47,7 +47,7 @@ Minimongo.Matcher = function (selector, isUpdate = false) { self._isUpdate = isUpdate; }; -_.extend(Minimongo.Matcher.prototype, { +Object.assign(Minimongo.Matcher.prototype, { documentMatches: function (doc) { if (!doc || typeof doc !== "object") { throw Error("documentMatches needs a document"); @@ -109,7 +109,7 @@ _.extend(Minimongo.Matcher.prototype, { // Returns a list of key paths the given selector is looking for. It includes // the empty string if there is a $where. _getPaths: function () { - return _.keys(this._paths); + return Object.keys(this._paths); } }); @@ -124,11 +124,12 @@ _.extend(Minimongo.Matcher.prototype, { var compileDocumentSelector = function (docSelector, matcher, options) { options = options || {}; var docMatchers = []; - _.each(docSelector, function (subSelector, key) { + Object.keys(docSelector).forEach(function (key) { + var subSelector = docSelector[key]; if (key.substr(0, 1) === '$') { // Outer operators are either logical operators (they recurse back into // this function), or $where. - if (!_.has(LOGICAL_OPERATORS, key)) + if (!LOGICAL_OPERATORS.hasOwnProperty(key)) throw new Error("Unrecognized logical operator: " + key); matcher._isSimple = false; docMatchers.push(LOGICAL_OPERATORS[key](subSelector, matcher, @@ -182,7 +183,7 @@ var convertElementMatcherToBranchedMatcher = function ( branches, options.dontIncludeLeafArrays); } var ret = {}; - ret.result = _.any(expanded, function (element) { + ret.result = expanded.some(function (element) { var matched = elementMatcher(element.value); // Special case for $elemMatch: it means "true, and use this as an array @@ -211,9 +212,7 @@ var convertElementMatcherToBranchedMatcher = function ( regexpElementMatcher = function (regexp) { return function (value) { if (value instanceof RegExp) { - // Comparing two regexps means seeing if the regexps are identical - // (really!). Underscore knows how. - return _.isEqual(value, regexp); + return value.toString() === regexp.toString(); } // Regexps only work against strings. if (typeof value !== 'string') @@ -258,21 +257,22 @@ var operatorBranchedMatcher = function (valueSelector, matcher, isRoot) { // is OK. var operatorMatchers = []; - _.each(valueSelector, function (operand, operator) { - var simpleRange = _.contains(['$lt', '$lte', '$gt', '$gte'], operator) && - _.isNumber(operand); - var simpleEquality = _.contains(['$ne', '$eq'], operator) && !_.isObject(operand); - var simpleInclusion = _.contains(['$in', '$nin'], operator) && - _.isArray(operand) && !_.any(operand, _.isObject); + Object.keys(valueSelector).forEach(function (operator) { + var operand = valueSelector[operator]; + var simpleRange = ['$lt', '$lte', '$gt', '$gte'].includes(operator) && + typeof operand === 'number'; + var simpleEquality = ['$ne', '$eq'].includes(operator) && operand !== Object(operand); + var simpleInclusion = ['$in', '$nin'].includes(operator) && + Array.isArray(operand) && !operand.some(function (x) { return x === Object(x); }); if (! (simpleRange || simpleInclusion || simpleEquality)) { matcher._isSimple = false; } - if (_.has(VALUE_OPERATORS, operator)) { + if (VALUE_OPERATORS.hasOwnProperty(operator)) { operatorMatchers.push( VALUE_OPERATORS[operator](operand, valueSelector, matcher, isRoot)); - } else if (_.has(ELEMENT_OPERATORS, operator)) { + } else if (ELEMENT_OPERATORS.hasOwnProperty(operator)) { var options = ELEMENT_OPERATORS[operator]; operatorMatchers.push( convertElementMatcherToBranchedMatcher( @@ -289,9 +289,9 @@ var operatorBranchedMatcher = function (valueSelector, matcher, isRoot) { var compileArrayOfDocumentSelectors = function ( selectors, matcher, inElemMatch) { - if (!isArray(selectors) || _.isEmpty(selectors)) + if (!isArray(selectors) || selectors.length === 0) throw Error("$and/$or/$nor must be nonempty array"); - return _.map(selectors, function (subSelector) { + return selectors.map(function (subSelector) { if (!isPlainObject(subSelector)) throw Error("$or/$and/$nor entries need to be full objects"); return compileDocumentSelector( @@ -317,7 +317,7 @@ var LOGICAL_OPERATORS = { return matchers[0]; return function (doc) { - var result = _.any(matchers, function (f) { + var result = matchers.some(function (f) { return f(doc).result; }); // $or does NOT set arrayIndices when it has multiple @@ -330,7 +330,7 @@ var LOGICAL_OPERATORS = { var matchers = compileArrayOfDocumentSelectors( subSelector, matcher, inElemMatch); return function (doc) { - var result = _.all(matchers, function (f) { + var result = matchers.every(function (f) { return !f(doc).result; }); // Never set arrayIndices, because we only match if nothing in particular @@ -405,7 +405,7 @@ var VALUE_OPERATORS = { }, // $options just provides options for $regex; its logic is inside $regex $options: function (operand, valueSelector) { - if (!_.has(valueSelector, '$regex')) + if (!valueSelector.hasOwnProperty('$regex')) throw Error("$options needs a $regex"); return everythingMatcher; }, @@ -419,11 +419,11 @@ var VALUE_OPERATORS = { if (!isArray(operand)) throw Error("$all requires array"); // Not sure why, but this seems to be what MongoDB does. - if (_.isEmpty(operand)) + if (operand.length === 0) return nothingMatcher; var branchedMatchers = []; - _.each(operand, function (criterion) { + operand.forEach(function (criterion) { // XXX handle $all/$elemMatch combination if (isOperatorObject(criterion)) throw Error("no $ expressions in $all"); @@ -445,7 +445,7 @@ var VALUE_OPERATORS = { // matched using $geometry. var maxDistance, point, distance; - if (isPlainObject(operand) && _.has(operand, '$geometry')) { + if (isPlainObject(operand) && operand.hasOwnProperty('$geometry')) { // GeoJSON "2dsphere" mode. maxDistance = operand.$maxDistance; point = operand.$geometry; @@ -488,7 +488,7 @@ var VALUE_OPERATORS = { // each within-$maxDistance branching point. branchedValues = expandArraysInBranches(branchedValues); var result = {result: false}; - _.every(branchedValues, function (branch) { + branchedValues.every(function (branch) { // if operation is an update, don't skip branches, just return the first one (#3599) if (!matcher._isUpdate){ if (!(typeof branch.value === "object")){ @@ -523,7 +523,7 @@ var distanceCoordinatePairs = function (a, b) { b = pointToArray(b); var x = a[0] - b[0]; var y = a[1] - b[1]; - if (_.isNaN(x) || _.isNaN(y)) + if (Number.isNaN(x) || Number.isNaN(y)) return null; return Math.sqrt(x * x + y * y); }; @@ -531,7 +531,7 @@ var distanceCoordinatePairs = function (a, b) { // the second one to y no matter what user passes. // In case user passes { lon: x, lat: y } returns [x, y] var pointToArray = function (point) { - return _.map(point, _.identity); + return Array.isArray(point) ? point.slice() : [point.x, point.y]; }; // Helper for $lt/$gt/$lte/$gte. @@ -671,7 +671,7 @@ ELEMENT_OPERATORS = { throw Error("$in needs an array"); var elementMatchers = []; - _.each(operand, function (option) { + operand.forEach(function (option) { if (option instanceof RegExp) elementMatchers.push(regexpElementMatcher(option)); else if (isOperatorObject(option)) @@ -684,7 +684,7 @@ ELEMENT_OPERATORS = { // Allow {a: {$in: [null]}} to match when 'a' does not exist. if (value === undefined) value = null; - return _.any(elementMatchers, function (e) { + return elementMatchers.some(function (e) { return e(value); }); }; @@ -801,7 +801,7 @@ ELEMENT_OPERATORS = { throw Error("$elemMatch need an object"); var subMatcher, isDocMatcher; - if (isOperatorObject(_.omit(operand, _.keys(LOGICAL_OPERATORS)), true)) { + if (isOperatorObject(Object.keys(operand).filter(key => !Object.keys(LOGICAL_OPERATORS).includes(key)).reduce(function (a, b) { return Object.assign(a, {[b]: operand[b]}); }, {}), true)) { subMatcher = compileValueSelector(operand, matcher); isDocMatcher = false; } else { @@ -993,7 +993,7 @@ makeLookupFunction = function (key, options) { // "look up this index" in that case, not "also look up this index in all // the elements of the array". if (isArray(firstLevel) && !(nextPartIsNumeric && options.forSort)) { - _.each(firstLevel, function (branch, arrayIndex) { + firstLevel.forEach(function (branch, arrayIndex) { if (isPlainObject(branch)) { appendToResult(lookupRest( branch, @@ -1009,7 +1009,7 @@ MinimongoTest.makeLookupFunction = makeLookupFunction; expandArraysInBranches = function (branches, skipTheArrays) { var branchesOut = []; - _.each(branches, function (branch) { + branches.forEach(function (branch) { var thisIsArray = isArray(branch.value); // We include the branch itself, *UNLESS* we it's an array that we're going // to iterate and we're told to skip arrays. (That's right, we include some @@ -1022,7 +1022,7 @@ expandArraysInBranches = function (branches, skipTheArrays) { }); } if (thisIsArray && !branch.dontIterate) { - _.each(branch.value, function (leaf, i) { + branch.value.forEach(function (leaf, i) { branchesOut.push({ value: leaf, arrayIndices: (branch.arrayIndices || []).concat(i) @@ -1054,7 +1054,7 @@ var andSomeMatchers = function (subMatchers) { return function (docOrBranches) { var ret = {}; - ret.result = _.all(subMatchers, function (f) { + ret.result = subMatchers.every(function (f) { var subResult = f(docOrBranches); // Copy a 'distance' number out of the first sub-matcher that has // one. Yes, this means that if there are multiple $near fields in a diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 54b37df68b..32c9f79a6f 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -9,13 +9,13 @@ Minimongo.Matcher.prototype.affectedByModifier = function (modifier) { var self = this; // safe check for $set/$unset being objects - modifier = _.extend({ $set: {}, $unset: {} }, modifier); - var modifiedPaths = _.keys(modifier.$set).concat(_.keys(modifier.$unset)); + modifier = Object.assign({ $set: {}, $unset: {} }, modifier); + var modifiedPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset)); var meaningfulPaths = self._getPaths(); - return _.any(modifiedPaths, function (path) { + return modifiedPaths.some(function (path) { var mod = path.split('.'); - return _.any(meaningfulPaths, function (meaningfulPath) { + return meaningfulPaths.some(function (meaningfulPath) { var sel = meaningfulPath.split('.'); var i = 0, j = 0; @@ -64,14 +64,14 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { if (!this.affectedByModifier(modifier)) return false; - modifier = _.extend({$set:{}, $unset:{}}, modifier); - var modifierPaths = _.keys(modifier.$set).concat(_.keys(modifier.$unset)); + modifier = Object.assign({$set:{}, $unset:{}}, modifier); + var modifierPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset)); if (!self.isSimple()) return true; - if (_.any(self._getPaths(), pathHasNumericKeys) || - _.any(modifierPaths, pathHasNumericKeys)) + if (self._getPaths().some(pathHasNumericKeys) || + modifierPaths.some(pathHasNumericKeys)) return true; // check if there is a $set or $unset that indicates something is an @@ -79,10 +79,11 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // NOTE: it is correct since we allow only scalars in $-operators // Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would // definitely set the result to false as 'a.b' appears to be an object. - var expectedScalarIsObject = _.any(self._selector, function (sel, path) { + var expectedScalarIsObject = Object.keys(self._selector).some(function (path) { + var sel = self._selector[path]; if (! isOperatorObject(sel)) return false; - return _.any(modifierPaths, function (modifierPath) { + return modifierPaths.some(function (modifierPath) { return startsWith(modifierPath, path + '.'); }); }); @@ -149,17 +150,17 @@ Minimongo.Matcher.prototype.matchingDocument = function () { // Return anything from $in that matches the whole selector for this // path. If nothing matches, returns `undefined` as nothing can make // this selector into `true`. - return _.find(valueSelector.$in, function (x) { + return valueSelector.$in.find(function (x) { return matcher.documentMatches({ placeholder: x }).result; }); } else if (onlyContainsKeys(valueSelector, ['$gt', '$gte', '$lt', '$lte'])) { var lowerBound = -Infinity, upperBound = Infinity; - _.each(['$lte', '$lt'], function (op) { - if (_.has(valueSelector, op) && valueSelector[op] < upperBound) + ['$lte', '$lt'].forEach(function (op) { + if (valueSelector.hasOwnProperty(op) && valueSelector[op] < upperBound) upperBound = valueSelector[op]; }); - _.each(['$gte', '$gt'], function (op) { - if (_.has(valueSelector, op) && valueSelector[op] > lowerBound) + ['$gte', '$gt'].forEach(function (op) { + if (valueSelector.hasOwnProperty(op) && valueSelector[op] > lowerBound) lowerBound = valueSelector[op]; }); @@ -181,7 +182,7 @@ Minimongo.Matcher.prototype.matchingDocument = function () { } return self._selector[path]; }, - _.identity /*conflict resolution is no resolution*/); + function (x) { return x; } /*conflict resolution is no resolution*/); if (fallback) self._matchingDocument = null; @@ -190,28 +191,31 @@ Minimongo.Matcher.prototype.matchingDocument = function () { }; var getPaths = function (sel) { - return _.keys(new Minimongo.Matcher(sel)._paths); - return _.chain(sel).map(function (v, k) { + return Object.keys(new Minimongo.Matcher(sel)._paths); + return Object.keys(sel).map(function (k) { + var v = sel[k]; // we don't know how to handle $where because it can be anything if (k === "$where") return ''; // matches everything // we branch from $or/$and/$nor operator - if (_.contains(['$or', '$and', '$nor'], k)) - return _.map(v, getPaths); + if (['$or', '$and', '$nor'].includes(k)) + return v.map(getPaths); // the value is a literal or some comparison operator return k; - }).flatten().uniq().value(); + }) + .reduce(function (a, b) { return a.concat(b); }, []) + .filter(function (a, b, c) { return c.indexOf(a) === b; }); }; // A helper to ensure object has only certain keys var onlyContainsKeys = function (obj, keys) { - return _.all(obj, function (v, k) { - return _.contains(keys, k); + return Object.keys(obj).every(function (k) { + return keys.includes(k); }); }; var pathHasNumericKeys = function (path) { - return _.any(path.split('.'), isNumericKey); + return path.split('.').some(isNumericKey); } // XXX from Underscore.String (http://epeli.github.com/underscore.string/) diff --git a/packages/minimongo/selector_projection.js b/packages/minimongo/selector_projection.js index 5f6e101b5b..670c529384 100644 --- a/packages/minimongo/selector_projection.js +++ b/packages/minimongo/selector_projection.js @@ -9,7 +9,7 @@ Minimongo.Matcher.prototype.combineIntoProjection = function (projection) { // on all fields of the document. getSelectorPaths returns a list of paths // selector depends on. If one of the paths is '' (empty string) representing // the root or the whole document, complete projection should be returned. - if (_.contains(selectorPaths, '')) + if (selectorPaths.includes('')) return {}; return combineImportantPathsIntoProjection(selectorPaths, projection); @@ -17,8 +17,8 @@ Minimongo.Matcher.prototype.combineIntoProjection = function (projection) { Minimongo._pathsElidingNumericKeys = function (paths) { var self = this; - return _.map(paths, function (path) { - return _.reject(path.split('.'), isNumericKey).join('.'); + return paths.map(function (path) { + return path.split('.').filter(function (part) { return !isNumericKey(part); }).join('.'); }); }; @@ -42,7 +42,8 @@ combineImportantPathsIntoProjection = function (paths, projection) { // projection is pointing at fields to exclude // make sure we don't exclude important paths var mergedExclProjection = {}; - _.each(mergedProjection, function (incl, path) { + Object.keys(mergedProjection).forEach(function (path) { + var incl = mergedProjection[path]; if (!incl) mergedExclProjection[path] = false; }); @@ -57,9 +58,10 @@ var treeToPaths = function (tree, prefix) { prefix = prefix || ''; var result = {}; - _.each(tree, function (val, key) { - if (_.isObject(val)) - _.extend(result, treeToPaths(val, prefix + key + '.')); + Object.keys(tree).forEach(function (key) { + var val = tree[key]; + if (val === Object(val)) + Object.assign(result, treeToPaths(val, prefix + key + '.')); else result[prefix + key] = val; }); diff --git a/packages/minimongo/sort.js b/packages/minimongo/sort.js index aebbcc2d4f..95ac434379 100644 --- a/packages/minimongo/sort.js +++ b/packages/minimongo/sort.js @@ -39,7 +39,8 @@ Minimongo.Sorter = function (spec, options) { } } } else if (typeof spec === "object") { - _.each(spec, function (value, key) { + Object.keys(spec).forEach(function (key) { + var value = spec[key]; addSpecPart(key, value >= 0); }); } else if (typeof spec === "function") { @@ -57,14 +58,14 @@ Minimongo.Sorter = function (spec, options) { // modifiers as this sort order. This is only implemented on the server. if (self.affectedByModifier) { var selector = {}; - _.each(self._sortSpecParts, function (spec) { + self._sortSpecParts.forEach(function (spec) { selector[spec.path] = 1; }); self._selectorForAffectedByModifier = new Minimongo.Matcher(selector); } self._keyComparator = composeComparators( - _.map(self._sortSpecParts, function (spec, i) { + self._sortSpecParts.map(function (spec, i) { return self._keyFieldComparator(i); })); @@ -77,11 +78,11 @@ Minimongo.Sorter = function (spec, options) { // In addition to these methods, sorter_project.js defines combineIntoProjection // on the server only. -_.extend(Minimongo.Sorter.prototype, { +Object.assign(Minimongo.Sorter.prototype, { getComparator: function (options) { var self = this; - // If sort is specified or have no distances, just use the comparator from + // If sort is specified or have no distances, just use the comparator from // the source specification (which defaults to "everything is equal". // issue #3599 // https://docs.mongodb.com/manual/reference/operator/query/near/#sort-operation @@ -104,7 +105,7 @@ _.extend(Minimongo.Sorter.prototype, { _getPaths: function () { var self = this; - return _.pluck(self._sortSpecParts, 'path'); + return self._sortSpecParts.map(function (part) { return part.path; }); }, // Finds the minimum key from the doc, according to the sort specs. (We say @@ -163,7 +164,7 @@ _.extend(Minimongo.Sorter.prototype, { var knownPaths = null; - _.each(self._sortSpecParts, function (spec, whichField) { + self._sortSpecParts.forEach(function (spec, whichField) { // Expand any leaf arrays that we find, and ignore those arrays // themselves. (We never sort based on an array itself.) var branches = expandArraysInBranches(spec.lookup(doc), true); @@ -175,7 +176,7 @@ _.extend(Minimongo.Sorter.prototype, { var usedPaths = false; valuesByIndexAndPath[whichField] = {}; - _.each(branches, function (branch) { + branches.forEach(function (branch) { if (!branch.arrayIndices) { // If there are no array indices for a branch, then it must be the // only branch, because the only thing that produces multiple branches @@ -188,7 +189,7 @@ _.extend(Minimongo.Sorter.prototype, { usedPaths = true; var path = pathFromIndices(branch.arrayIndices); - if (_.has(valuesByIndexAndPath[whichField], path)) + if (valuesByIndexAndPath[whichField].hasOwnProperty(path)) throw Error("duplicate path: " + path); valuesByIndexAndPath[whichField][path] = branch.value; @@ -202,7 +203,7 @@ _.extend(Minimongo.Sorter.prototype, { // and 'a.x.y' are both arrays, but we don't allow this for now. // #NestedArraySort // XXX achieve full compatibility here - if (knownPaths && !_.has(knownPaths, path)) { + if (knownPaths && !knownPaths.hasOwnProperty(path)) { throw Error("cannot index parallel arrays"); } }); @@ -210,13 +211,13 @@ _.extend(Minimongo.Sorter.prototype, { if (knownPaths) { // Similarly to above, paths must match everywhere, unless this is a // non-array field. - if (!_.has(valuesByIndexAndPath[whichField], '') && - _.size(knownPaths) !== _.size(valuesByIndexAndPath[whichField])) { + if (!valuesByIndexAndPath[whichField].hasOwnProperty('') && + Object.keys(knownPaths).length !== Object.keys(valuesByIndexAndPath[whichField]).length) { throw Error("cannot index parallel arrays!"); } } else if (usedPaths) { knownPaths = {}; - _.each(valuesByIndexAndPath[whichField], function (x, path) { + Object.keys(valuesByIndexAndPath[whichField]).forEach(function (path) { knownPaths[path] = true; }); } @@ -224,8 +225,8 @@ _.extend(Minimongo.Sorter.prototype, { if (!knownPaths) { // Easy case: no use of arrays. - var soleKey = _.map(valuesByIndexAndPath, function (values) { - if (!_.has(values, '')) + var soleKey = valuesByIndexAndPath.map(function (values) { + if (!values.hasOwnProperty('')) throw Error("no value in sole key case?"); return values['']; }); @@ -233,11 +234,11 @@ _.extend(Minimongo.Sorter.prototype, { return; } - _.each(knownPaths, function (x, path) { - var key = _.map(valuesByIndexAndPath, function (values) { - if (_.has(values, '')) + Object.keys(knownPaths).forEach(function (path) { + var key = valuesByIndexAndPath.map(function (values) { + if (values.hasOwnProperty('')) return values['']; - if (!_.has(values, path)) + if (!values.hasOwnProperty(path)) throw Error("missing path?"); return values[path]; }); @@ -322,7 +323,7 @@ _.extend(Minimongo.Sorter.prototype, { // If we are only sorting by distance, then we're not going to bother to // build a key filter. // XXX figure out how geoqueries interact with this stuff - if (_.isEmpty(self._sortSpecParts)) + if (!self._sortSpecParts.length) return; var selector = matcher._selector; @@ -333,11 +334,12 @@ _.extend(Minimongo.Sorter.prototype, { return; var constraintsByPath = {}; - _.each(self._sortSpecParts, function (spec, i) { + self._sortSpecParts.forEach(function (spec, i) { constraintsByPath[spec.path] = []; }); - _.each(selector, function (subSelector, key) { + Object.keys(selector).forEach(function (key) { + var subSelector = selector[key]; // XXX support $and and $or var constraints = constraintsByPath[key]; @@ -362,8 +364,9 @@ _.extend(Minimongo.Sorter.prototype, { } if (isOperatorObject(subSelector)) { - _.each(subSelector, function (operand, operator) { - if (_.contains(['$lt', '$lte', '$gt', '$gte'], operator)) { + Object.keys(subSelector).forEach(function (operator) { + var operand = subSelector[operator]; + if (['$lt', '$lte', '$gt', '$gte'].includes(operator)) { // XXX this depends on us knowing that these operators don't use any // of the arguments to compileElementSelector other than operand. constraints.push( @@ -390,12 +393,12 @@ _.extend(Minimongo.Sorter.prototype, { // others; we shouldn't create a key filter unless the first sort field is // restricted, though after that point we can restrict the other sort fields // or not as we wish. - if (_.isEmpty(constraintsByPath[self._sortSpecParts[0].path])) + if (!constraintsByPath[self._sortSpecParts[0].path].length) return; self._keyFilter = function (key) { - return _.all(self._sortSpecParts, function (specPart, index) { - return _.all(constraintsByPath[specPart.path], function (f) { + return self._sortSpecParts.every(function (specPart, index) { + return constraintsByPath[specPart.path].every(function (f) { return f(key[index]); }); }); diff --git a/packages/minimongo/validation.js b/packages/minimongo/validation.js index ef73560a72..7728361f76 100644 --- a/packages/minimongo/validation.js +++ b/packages/minimongo/validation.js @@ -8,7 +8,7 @@ const invalidCharMsg = { }; export function assertIsValidFieldName(key) { let match; - if (_.isString(key) && (match = key.match(/^\$|\.|\0/))) { + if (typeof key === 'string' && (match = key.match(/^\$|\.|\0/))) { throw MinimongoError(`Key ${key} must not ${invalidCharMsg[match[0]]}`); } }; @@ -21,4 +21,4 @@ export function assertHasValidFieldNames(doc){ return value; }); } -}; \ No newline at end of file +}; diff --git a/packages/minimongo/wrap_transform.js b/packages/minimongo/wrap_transform.js index 561931e378..797fd3241c 100644 --- a/packages/minimongo/wrap_transform.js +++ b/packages/minimongo/wrap_transform.js @@ -16,7 +16,7 @@ LocalCollection.wrapTransform = function (transform) { return transform; var wrapped = function (doc) { - if (!_.has(doc, '_id')) { + if (!doc.hasOwnProperty('_id')) { // XXX do we ever have a transform on the oplog's collection? because that // collection has no _id. throw new Error("can only transform documents with _id"); @@ -32,7 +32,7 @@ LocalCollection.wrapTransform = function (transform) { throw new Error("transform must return object"); } - if (_.has(transformed, '_id')) { + if (transformed.hasOwnProperty('_id')) { if (!EJSON.equals(transformed._id, id)) { throw new Error("transformed document can't have different _id"); } diff --git a/packages/minimongo/wrap_transform_tests.js b/packages/minimongo/wrap_transform_tests.js index 6385a29120..3c1f84196a 100644 --- a/packages/minimongo/wrap_transform_tests.js +++ b/packages/minimongo/wrap_transform_tests.js @@ -13,7 +13,7 @@ Tinytest.add("minimongo - wrapTransform", function (test) { return doc; }; var transformed = wrap(validTransform)({_id: "asdf", x: 54}); - test.equal(_.keys(transformed), ['_id', 'y', 'z']); + test.equal(Object.keys(transformed), ['_id', 'y', 'z']); test.equal(transformed.y, 42); test.equal(transformed.z(), 43); @@ -28,7 +28,7 @@ Tinytest.add("minimongo - wrapTransform", function (test) { "asdf", new MongoID.ObjectID(), false, null, true, 27, [123], /adsf/, new Date, function () {}, undefined ]; - _.each(invalidObjects, function (invalidObject) { + invalidObjects.forEach(function (invalidObject) { var wrapped = wrap(function () { return invalidObject; }); test.throws(function () { wrapped({_id: "asdf"});