From e1ef1e298401bb0927b9da2aca039c2a7fead99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Miernik?= Date: Wed, 12 Jul 2017 00:18:20 +0200 Subject: [PATCH] Got rid of self (sic!). --- packages/minimongo/cursor.js | 163 ++++++++---------- packages/minimongo/local_collection.js | 218 +++++++++++-------------- packages/minimongo/main_server.js | 44 ++--- packages/minimongo/sorter.js | 90 +++++----- 4 files changed, 225 insertions(+), 290 deletions(-) diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index 364ad26adb..05dfb1aa6e 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -4,39 +4,36 @@ import {LocalCollection} from './local_collection.js'; // a defined order, limit, and offset. creating a Cursor with LocalCollection.find(), export class Cursor { // don't call this ctor directly. use LocalCollection.find(). - constructor (collection, selector, options) { - const self = this; - if (!options) options = {}; - - self.collection = collection; - self.sorter = null; - self.matcher = new Minimongo.Matcher(selector); + constructor (collection, selector, options = {}) { +this.collection = collection; + this.sorter = null; + this.matcher = new Minimongo.Matcher(selector); if (LocalCollection._selectorIsId(selector)) { // stash for fast path - self._selectorId = selector; + this._selectorId = selector; } else if (LocalCollection._selectorIsIdPerhapsAsObject(selector)) { // also do the fast path for { _id: idString } - self._selectorId = selector._id; + this._selectorId = selector._id; } else { - self._selectorId = undefined; - if (self.matcher.hasGeoQuery() || options.sort) { - self.sorter = new Minimongo.Sorter(options.sort || [], - { matcher: self.matcher }); + this._selectorId = undefined; + if (this.matcher.hasGeoQuery() || options.sort) { + this.sorter = new Minimongo.Sorter(options.sort || [], + { matcher: this.matcher }); } } - self.skip = options.skip; - self.limit = options.limit; - self.fields = options.fields; + this.skip = options.skip; + this.limit = options.limit; + this.fields = options.fields; - self._projectionFn = LocalCollection._compileProjection(self.fields || {}); + this._projectionFn = LocalCollection._compileProjection(this.fields || {}); - self._transform = LocalCollection.wrapTransform(options.transform); + this._transform = LocalCollection.wrapTransform(options.transform); // by default, queries register w/ Tracker when it is available. if (typeof Tracker !== "undefined") - self.reactive = (options.reactive === undefined) ? true : options.reactive; + this.reactive = (options.reactive === undefined) ? true : options.reactive; } /** @@ -48,13 +45,11 @@ export class Cursor { * @returns {Number} */ count () { - const self = this; - - if (self.reactive) - self._depend({added: true, removed: true}, + if (this.reactive) + this._depend({added: true, removed: true}, true /* allow the observe to be unordered */); - return self._getRawObjects({ordered: true}).length; + return this._getRawObjects({ordered: true}).length; } /** @@ -66,9 +61,8 @@ export class Cursor { * @returns {Object[]} */ fetch () { - const self = this; const res = []; - self.forEach(doc => { + this.forEach(doc => { res.push(doc); }); return res; @@ -89,12 +83,10 @@ export class Cursor { * @param {Any} [thisArg] An object which will be the value of `this` inside `callback`. */ forEach (callback, thisArg) { - const self = this; + const objects = this._getRawObjects({ordered: true}); - const objects = self._getRawObjects({ordered: true}); - - if (self.reactive) { - self._depend({ + if (this.reactive) { + this._depend({ addedBefore: true, removed: true, changed: true, @@ -103,11 +95,11 @@ export class Cursor { objects.forEach((elt, i) => { // This doubles as a clone operation. - elt = self._projectionFn(elt); + elt = this._projectionFn(elt); - if (self._transform) - elt = self._transform(elt); - callback.call(thisArg, elt, i, self); + if (this._transform) + elt = this._transform(elt); + callback.call(thisArg, elt, i, this); }); } @@ -125,10 +117,9 @@ export class Cursor { * @param {Any} [thisArg] An object which will be the value of `this` inside `callback`. */ map (callback, thisArg) { - const self = this; const res = []; - self.forEach((doc, index) => { - res.push(callback.call(thisArg, doc, index, self)); + this.forEach((doc, index) => { + res.push(callback.call(thisArg, doc, index, this)); }); return res; } @@ -162,8 +153,7 @@ export class Cursor { * @param {Object} callbacks Functions to call to deliver the result set as it changes */ observe (options) { - const self = this; - return LocalCollection._observeFromObserveChanges(self, options); + return LocalCollection._observeFromObserveChanges(this, options); } /** @@ -174,42 +164,40 @@ export class Cursor { * @param {Object} callbacks Functions to call to deliver the result set as it changes */ observeChanges (options) { - const self = this; - const ordered = LocalCollection._observeChangesCallbacksAreOrdered(options); // there are several places that assume you aren't combining skip/limit with // unordered observe. eg, update's EJSON.clone, and the "there are several" // comment in _modifyAndNotify // XXX allow skip/limit with unordered observe - if (!options._allow_unordered && !ordered && (self.skip || self.limit)) + if (!options._allow_unordered && !ordered && (this.skip || this.limit)) throw new Error("must use ordered observe (ie, 'addedBefore' instead of 'added') with skip or limit"); - if (self.fields && (self.fields._id === 0 || self.fields._id === false)) + if (this.fields && (this.fields._id === 0 || this.fields._id === false)) throw Error("You may not observe a cursor with {fields: {_id: 0}}"); const query = { dirty: false, - matcher: self.matcher, // not fast pathed - sorter: ordered && self.sorter, + matcher: this.matcher, // not fast pathed + sorter: ordered && this.sorter, distances: ( - self.matcher.hasGeoQuery() && ordered && new LocalCollection._IdMap), + this.matcher.hasGeoQuery() && ordered && new LocalCollection._IdMap), resultsSnapshot: null, ordered, - cursor: self, - projectionFn: self._projectionFn + cursor: this, + projectionFn: this._projectionFn }; let qid; // Non-reactive queries call added[Before] and then never call anything // else. - if (self.reactive) { - qid = self.collection.next_qid++; - self.collection.queries[qid] = query; + if (this.reactive) { + qid = this.collection.next_qid++; + this.collection.queries[qid] = query; } - query.results = self._getRawObjects({ + query.results = this._getRawObjects({ ordered, distances: query.distances}); - if (self.collection.paused) + if (this.collection.paused) query.resultsSnapshot = (ordered ? [] : new LocalCollection._IdMap); // wrap callbacks we were passed. callbacks only fire when not paused and @@ -222,15 +210,15 @@ export class Cursor { const wrapCallback = f => { if (!f) return () => {}; + const self = this; return function (/*args*/) { - const context = this; const args = arguments; if (self.collection.paused) return; self.collection._observeQueue.queueTask(() => { - f.apply(context, args); + f.apply(this, args); }); }; }; @@ -242,7 +230,7 @@ export class Cursor { query.movedBefore = wrapCallback(options.movedBefore); } - if (!options._suppress_initial && !self.collection.paused) { + if (!options._suppress_initial && !this.collection.paused) { const results = query.results._map || query.results; Object.keys(results).forEach(key => { const doc = results[key]; @@ -250,21 +238,21 @@ export class Cursor { delete fields._id; if (ordered) - query.addedBefore(doc._id, self._projectionFn(fields), null); - query.added(doc._id, self._projectionFn(fields)); + query.addedBefore(doc._id, this._projectionFn(fields), null); + query.added(doc._id, this._projectionFn(fields)); }); } const handle = new LocalCollection.ObserveHandle; Object.assign(handle, { - collection: self.collection, - stop() { - if (self.reactive) - delete self.collection.queries[qid]; + collection: this.collection, + stop: () => { + if (this.reactive) + delete this.collection.queries[qid]; } }); - if (self.reactive && Tracker.active) { + if (this.reactive && Tracker.active) { // XXX in many cases, the same observe will be recreated when // the current autorun is rerun. we could save work by // letting it linger across rerun and potentially get @@ -276,7 +264,7 @@ export class Cursor { } // run the observe callbacks resulting from the initial contents // before we leave the observe. - self.collection._observeQueue.drain(); + this.collection._observeQueue.drain(); return handle; } @@ -290,8 +278,6 @@ export class Cursor { // XXX Maybe we need a version of observe that just calls a callback if // anything changed. _depend (changers, _allow_unordered) { - const self = this; - if (Tracker.active) { const v = new Tracker.Dependency; v.depend(); @@ -307,13 +293,12 @@ export class Cursor { }); // observeChanges will stop() when this computation is invalidated - self.observeChanges(options); + this.observeChanges(options); } } _getCollectionName () { - const self = this; - return self.collection.name; + return this.collection.name; } // Returns a collection of matching objects, but doesn't deep copy them. @@ -331,28 +316,25 @@ export class Cursor { // argument, this function will clear it and use it for this purpose (otherwise // it will just create its own _IdMap). The observeChanges implementation uses // this to remember the distances after this function returns. - _getRawObjects (options) { - const self = this; - options = options || {}; - + _getRawObjects (options = {}) { // XXX use OrderedDict instead of array, and make IdMap and OrderedDict // compatible const results = options.ordered ? [] : new LocalCollection._IdMap; // fast path for single ID value - if (self._selectorId !== undefined) { + if (this._selectorId !== undefined) { // If you have non-zero skip and ask for a single id, you get // nothing. This is so it matches the behavior of the '{_id: foo}' // path. - if (self.skip) + if (this.skip) return results; - const selectedDoc = self.collection._docs.get(self._selectorId); + const selectedDoc = this.collection._docs.get(this._selectorId); if (selectedDoc) { if (options.ordered) results.push(selectedDoc); else - results.set(self._selectorId, selectedDoc); + results.set(this._selectorId, selectedDoc); } return results; } @@ -363,7 +345,7 @@ export class Cursor { // live results set) object. in other cases, distances is only used inside // this function. let distances; - if (self.matcher.hasGeoQuery() && options.ordered) { + if (this.matcher.hasGeoQuery() && options.ordered) { if (options.distances) { distances = options.distances; distances.clear(); @@ -372,8 +354,8 @@ export class Cursor { } } - self.collection._docs.forEach((doc, id) => { - const matchResult = self.matcher.documentMatches(doc); + this.collection._docs.forEach((doc, id) => { + const matchResult = this.matcher.documentMatches(doc); if (matchResult.result) { if (options.ordered) { results.push(doc); @@ -385,8 +367,8 @@ export class Cursor { } // Fast path for limited unsorted queries. // XXX 'length' check here seems wrong for ordered - if (self.limit && !self.skip && !self.sorter && - results.length === self.limit) + if (this.limit && !this.skip && !this.sorter && + results.length === this.limit) return false; // break return true; // continue }); @@ -394,27 +376,26 @@ export class Cursor { if (!options.ordered) return results; - if (self.sorter) { - const comparator = self.sorter.getComparator({distances}); + if (this.sorter) { + const comparator = this.sorter.getComparator({distances}); results.sort(comparator); } - const idx_start = self.skip || 0; - const idx_end = self.limit ? (self.limit + idx_start) : results.length; + const idx_start = this.skip || 0; + const idx_end = this.limit ? (this.limit + idx_start) : results.length; return results.slice(idx_start, idx_end); } _publishCursor (sub) { - const self = this; - if (! self.collection.name) + if (! this.collection.name) throw new Error("Can't publish a cursor from a collection without a name."); - const collection = self.collection.name; + const collection = this.collection.name; // XXX minimongo should not depend on mongo-livedata! if (! Package.mongo) { throw new Error("Can't publish from Minimongo without the `mongo` package."); } - return Package.mongo.Mongo.Collection._publishCursor(self, sub, collection); + return Package.mongo.Mongo.Collection._publishCursor(this, sub, collection); } } diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 2d6fac61e0..baa0ee8be7 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -86,7 +86,6 @@ export class LocalCollection { // XXX possibly enforce that 'undefined' does not appear (we assume // this in our handling of null and $exists) insert (doc, callback) { - const self = this; doc = EJSON.clone(doc); assertHasValidFieldNames(doc); @@ -99,16 +98,16 @@ export class LocalCollection { } const id = doc._id; - if (self._docs.has(id)) + if (this._docs.has(id)) throw MinimongoError(`Duplicate _id '${id}'`); - self._saveOriginal(id, undefined); - self._docs.set(id, doc); + this._saveOriginal(id, undefined); + this._docs.set(id, doc); const queriesToRecompute = []; // trigger live queries that match - for (let qid in self.queries) { - const query = self.queries[qid]; + for (let qid in this.queries) { + const query = this.queries[qid]; if (query.dirty) continue; const matchResult = query.matcher.documentMatches(doc); if (matchResult.result) { @@ -122,10 +121,10 @@ export class LocalCollection { } queriesToRecompute.forEach(qid => { - if (self.queries[qid]) - self._recomputeResults(self.queries[qid]); + if (this.queries[qid]) + this._recomputeResults(this.queries[qid]); }); - self._observeQueue.drain(); + this._observeQueue.drain(); // Defer because the caller likely doesn't expect the callback to be run // immediately. @@ -155,16 +154,14 @@ export class LocalCollection { } remove (selector, callback) { - const self = this; - // Easy special case: if we're not calling observeChanges callbacks and we're // not saving originals and we got asked to remove everything, then just empty // everything directly. - if (self.paused && !self._savedOriginals && EJSON.equals(selector, {})) { - const result = self._docs.size(); - self._docs.clear(); - Object.keys(self.queries).forEach(qid => { - const query = self.queries[qid]; + if (this.paused && !this._savedOriginals && EJSON.equals(selector, {})) { + const result = this._docs.size(); + this._docs.clear(); + Object.keys(this.queries).forEach(qid => { + const query = this.queries[qid]; if (query.ordered) { query.results = []; } else { @@ -181,7 +178,7 @@ export class LocalCollection { const matcher = new Minimongo.Matcher(selector); const remove = []; - self._eachPossiblyMatchingDoc(selector, (doc, id) => { + this._eachPossiblyMatchingDoc(selector, (doc, id) => { if (matcher.documentMatches(doc).result) remove.push(id); }); @@ -190,9 +187,9 @@ export class LocalCollection { const queryRemove = []; for (let i = 0; i < remove.length; i++) { const removeId = remove[i]; - const removeDoc = self._docs.get(removeId); - Object.keys(self.queries).forEach(qid => { - const query = self.queries[qid]; + const removeDoc = this._docs.get(removeId); + Object.keys(this.queries).forEach(qid => { + const query = this.queries[qid]; if (query.dirty) return; if (query.matcher.documentMatches(removeDoc).result) { @@ -202,24 +199,24 @@ export class LocalCollection { queryRemove.push({qid, doc: removeDoc}); } }); - self._saveOriginal(removeId, removeDoc); - self._docs.remove(removeId); + this._saveOriginal(removeId, removeDoc); + this._docs.remove(removeId); } // run live query callbacks _after_ we've removed the documents. queryRemove.forEach(remove => { - const query = self.queries[remove.qid]; + const query = this.queries[remove.qid]; if (query) { query.distances && query.distances.remove(remove.doc._id); LocalCollection._removeFromResults(query, remove.doc); } }); queriesToRecompute.forEach(qid => { - const query = self.queries[qid]; + const query = this.queries[qid]; if (query) - self._recomputeResults(query); + this._recomputeResults(query); }); - self._observeQueue.drain(); + this._observeQueue.drain(); const result = remove.length; if (callback) Meteor.defer(() => { @@ -233,7 +230,6 @@ export class LocalCollection { // database. Note that this is not just replaying all the changes that // happened during the pause, it is a smarter 'coalesced' diff. resumeObservers () { - const self = this; // No-op if not paused. if (!this.paused) return; @@ -243,11 +239,11 @@ export class LocalCollection { this.paused = false; for (let qid in this.queries) { - const query = self.queries[qid]; + const query = this.queries[qid]; if (query.dirty) { query.dirty = false; // re-compute results will perform `LocalCollection._diffQueryChanges` automatically. - self._recomputeResults(query, query.resultsSnapshot); + this._recomputeResults(query, query.resultsSnapshot); } else { // Diff the current results against the snapshot and send to observers. // pass the query object for its observer callbacks. @@ -257,16 +253,15 @@ export class LocalCollection { } query.resultsSnapshot = null; } - self._observeQueue.drain(); + this._observeQueue.drain(); } retrieveOriginals () { - const self = this; - if (!self._savedOriginals) + if (!this._savedOriginals) throw new Error("Called retrieveOriginals without saveOriginals"); - const originals = self._savedOriginals; - self._savedOriginals = null; + const originals = this._savedOriginals; + this._savedOriginals = null; return originals; } @@ -278,16 +273,14 @@ export class LocalCollection { // is the value.) You must alternate between calls to saveOriginals() and // retrieveOriginals(). saveOriginals () { - const self = this; - if (self._savedOriginals) + if (this._savedOriginals) throw new Error("Called saveOriginals twice without retrieveOriginals"); - self._savedOriginals = new LocalCollection._IdMap; + this._savedOriginals = new LocalCollection._IdMap; } // XXX atomicity: if multi is true, and one modification fails, do // we rollback the whole operation, or what? update (selector, mod, options, callback) { - const self = this; if (! callback && options instanceof Function) { callback = options; options = null; @@ -306,9 +299,9 @@ export class LocalCollection { const docMap = new LocalCollection._IdMap; const idsMatchedBySelector = LocalCollection._idsMatchedBySelector(selector); - Object.keys(self.queries).forEach(qid => { - const query = self.queries[qid]; - if ((query.cursor.skip || query.cursor.limit) && ! self.paused) { + Object.keys(this.queries).forEach(qid => { + const query = this.queries[qid]; + if ((query.cursor.skip || query.cursor.limit) && ! this.paused) { // Catch the case of a reactive `count()` on a cursor with skip // or limit, which registers an unordered observe. This is a // pretty rare case, so we just clone the entire result set with @@ -351,12 +344,12 @@ export class LocalCollection { let updateCount = 0; - self._eachPossiblyMatchingDoc(selector, (doc, id) => { + this._eachPossiblyMatchingDoc(selector, (doc, id) => { const queryResult = matcher.documentMatches(doc); if (queryResult.result) { // XXX Should we save the original even if mod ends up being a no-op? - self._saveOriginal(id, doc); - self._modifyAndNotify(doc, mod, recomputeQids, queryResult.arrayIndices); + this._saveOriginal(id, doc); + this._modifyAndNotify(doc, mod, recomputeQids, queryResult.arrayIndices); ++updateCount; if (!options.multi) return false; // break @@ -365,11 +358,11 @@ export class LocalCollection { }); Object.keys(recomputeQids).forEach(qid => { - const query = self.queries[qid]; + const query = this.queries[qid]; if (query) - self._recomputeResults(query, qidToOriginalResults[qid]); + this._recomputeResults(query, qidToOriginalResults[qid]); }); - self._observeQueue.drain(); + this._observeQueue.drain(); // If we are doing an upsert, and we didn't modify any documents yet, then // it's time to do an insert. Figure out what document we are inserting, and @@ -396,7 +389,7 @@ export class LocalCollection { if (! newDoc._id && options.insertedId) newDoc._id = options.insertedId; - insertedId = self.insert(newDoc); + insertedId = this.insert(newDoc); updateCount = 1; } @@ -425,12 +418,11 @@ export class LocalCollection { // equivalent to LocalCollection.update(sel, mod, { upsert: true, _returnObject: // true }). upsert (selector, mod, options, callback) { - const self = this; if (! callback && typeof options === "function") { callback = options; options = {}; } - return self.update(selector, mod, Object.assign({}, options, { + return this.update(selector, mod, Object.assign({}, options, { upsert: true, _returnObject: true }), callback); @@ -441,12 +433,11 @@ export class LocalCollection { // specific _id's, it only looks at those. doc is *not* cloned: it is the // same object that is in _docs. _eachPossiblyMatchingDoc (selector, f) { - const self = this; const specificIds = LocalCollection._idsMatchedBySelector(selector); if (specificIds) { for (let i = 0; i < specificIds.length; ++i) { const id = specificIds[i]; - const doc = self._docs.get(id); + const doc = this._docs.get(id); if (doc) { const breakIfFalse = f(doc, id); if (breakIfFalse === false) @@ -454,16 +445,14 @@ export class LocalCollection { } } } else { - self._docs.forEach(f); + this._docs.forEach(f); } } _modifyAndNotify (doc, mod, recomputeQids, arrayIndices) { - const self = this; - const matched_before = {}; - for (let qid in self.queries) { - const query = self.queries[qid]; + for (let qid in this.queries) { + const query = this.queries[qid]; if (query.dirty) continue; if (query.ordered) { @@ -479,8 +468,8 @@ export class LocalCollection { LocalCollection._modify(doc, mod, {arrayIndices}); - for (let qid in self.queries) { - const query = self.queries[qid]; + for (let qid in this.queries) { + const query = this.queries[qid]; if (query.dirty) continue; const before = matched_before[qid]; @@ -520,8 +509,7 @@ export class LocalCollection { // // oldResults is guaranteed to be ignored if the query is not paused. _recomputeResults (query, oldResults) { - const self = this; - if (self.paused) { + if (this.paused) { // There's no reason to recompute the results now as we're still paused. // By flagging the query as "dirty", the recompute will be performed // when resumeObservers is called. @@ -529,14 +517,14 @@ export class LocalCollection { return; } - if (! self.paused && ! oldResults) + if (! this.paused && ! oldResults) oldResults = query.results; if (query.distances) query.distances.clear(); query.results = query.cursor._getRawObjects({ ordered: query.ordered, distances: query.distances}); - if (! self.paused) { + if (! this.paused) { LocalCollection._diffQueryChanges( query.ordered, oldResults, query.results, query, { projectionFn: query.projectionFn }); @@ -544,16 +532,15 @@ export class LocalCollection { } _saveOriginal (id, doc) { - const self = this; // Are we even trying to save originals? - if (!self._savedOriginals) + if (!this._savedOriginals) return; // Have we previously mutated the original (and so 'doc' is not actually // original)? (Note the 'has' check rather than truth: we store undefined // here for inserted docs!) - if (self._savedOriginals.has(id)) + if (this._savedOriginals.has(id)) return; - self._savedOriginals.set(id, EJSON.clone(doc)); + this._savedOriginals.set(id, EJSON.clone(doc)); } } @@ -564,76 +551,73 @@ LocalCollection.ObserveHandle = ObserveHandle; // XXX maybe move these into another ObserveHelpers package or something // _CachingChangeObserver is an object which receives observeChanges callbacks -// and keeps a cache of the current cursor state up to date in self.docs. Users +// and keeps a cache of the current cursor state up to date in this.docs. Users // of this class should read the docs field but not modify it. You should pass // the "applyChange" field as the callbacks to the underlying observeChanges // call. Optionally, you can specify your own observeChanges callbacks which are // invoked immediately before the docs field is updated; this object is made // available as `this` to those callbacks. LocalCollection._CachingChangeObserver = class _CachingChangeObserver { - constructor (options) { - const self = this; - options = options || {}; - + constructor (options = {}) { const orderedFromCallbacks = options.callbacks && LocalCollection._observeChangesCallbacksAreOrdered(options.callbacks); if (options.hasOwnProperty('ordered')) { - self.ordered = options.ordered; + this.ordered = options.ordered; if (options.callbacks && options.ordered !== orderedFromCallbacks) throw Error("ordered option doesn't match callbacks"); } else if (options.callbacks) { - self.ordered = orderedFromCallbacks; + this.ordered = orderedFromCallbacks; } else { throw Error("must provide ordered or callbacks"); } const callbacks = options.callbacks || {}; - if (self.ordered) { - self.docs = new OrderedDict(MongoID.idStringify); - self.applyChange = { - addedBefore(id, fields, before) { + if (this.ordered) { + this.docs = new OrderedDict(MongoID.idStringify); + this.applyChange = { + addedBefore: (id, fields, before) => { const doc = EJSON.clone(fields); doc._id = id; callbacks.addedBefore && callbacks.addedBefore.call( - self, id, fields, before); + this, id, fields, before); // This line triggers if we provide added with movedBefore. - callbacks.added && callbacks.added.call(self, id, fields); + callbacks.added && callbacks.added.call(this, id, fields); // XXX could `before` be a falsy ID? Technically // idStringify seems to allow for them -- though // OrderedDict won't call stringify on a falsy arg. - self.docs.putBefore(id, doc, before || null); + this.docs.putBefore(id, doc, before || null); }, - movedBefore(id, before) { - const doc = self.docs.get(id); - callbacks.movedBefore && callbacks.movedBefore.call(self, id, before); - self.docs.moveBefore(id, before || null); + movedBefore: (id, before) => { + const doc = this.docs.get(id); + callbacks.movedBefore && callbacks.movedBefore.call(this, id, before); + this.docs.moveBefore(id, before || null); } }; } else { - self.docs = new LocalCollection._IdMap; - self.applyChange = { - added(id, fields) { + this.docs = new LocalCollection._IdMap; + this.applyChange = { + added: (id, fields) => { const doc = EJSON.clone(fields); - callbacks.added && callbacks.added.call(self, id, fields); + callbacks.added && callbacks.added.call(this, id, fields); doc._id = id; - self.docs.set(id, doc); + this.docs.set(id, doc); } }; } // The methods in _IdMap and OrderedDict used by these callbacks are // identical. - self.applyChange.changed = (id, fields) => { - const doc = self.docs.get(id); + this.applyChange.changed = (id, fields) => { + const doc = this.docs.get(id); if (!doc) throw new Error(`Unknown id for changed: ${id}`); callbacks.changed && callbacks.changed.call( - self, id, EJSON.clone(fields)); + this, id, EJSON.clone(fields)); DiffSequence.applyChanges(doc, fields); }; - self.applyChange.removed = id => { - callbacks.removed && callbacks.removed.call(self, id); - self.docs.remove(id); + this.applyChange.removed = id => { + callbacks.removed && callbacks.removed.call(this, id); + this.docs.remove(id); }; } }; @@ -987,60 +971,56 @@ LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => { // relative order, transforms, and applyChanges -- without the speed hit. const indices = !observeCallbacks._no_indices; observeChangesCallbacks = { - addedBefore(id, fields, before) { - const self = this; + addedBefore (id, fields, before) { if (suppressed || !(observeCallbacks.addedAt || observeCallbacks.added)) return; const doc = transform(Object.assign(fields, {_id: id})); if (observeCallbacks.addedAt) { const index = indices - ? (before ? self.docs.indexOf(before) : self.docs.size()) : -1; + ? (before ? this.docs.indexOf(before) : this.docs.size()) : -1; observeCallbacks.addedAt(doc, index, before); } else { observeCallbacks.added(doc); } }, - changed(id, fields) { - const self = this; + changed (id, fields) { if (!(observeCallbacks.changedAt || observeCallbacks.changed)) return; - let doc = EJSON.clone(self.docs.get(id)); + let doc = EJSON.clone(this.docs.get(id)); if (!doc) throw new Error(`Unknown id for changed: ${id}`); const oldDoc = transform(EJSON.clone(doc)); DiffSequence.applyChanges(doc, fields); doc = transform(doc); if (observeCallbacks.changedAt) { - const index = indices ? self.docs.indexOf(id) : -1; + const index = indices ? this.docs.indexOf(id) : -1; observeCallbacks.changedAt(doc, oldDoc, index); } else { observeCallbacks.changed(doc, oldDoc); } }, - movedBefore(id, before) { - const self = this; + movedBefore (id, before) { if (!observeCallbacks.movedTo) return; - const from = indices ? self.docs.indexOf(id) : -1; + const from = indices ? this.docs.indexOf(id) : -1; let to = indices - ? (before ? self.docs.indexOf(before) : self.docs.size()) : -1; + ? (before ? this.docs.indexOf(before) : this.docs.size()) : -1; // When not moving backwards, adjust for the fact that removing the // document slides everything back one slot. if (to > from) --to; - observeCallbacks.movedTo(transform(EJSON.clone(self.docs.get(id))), + observeCallbacks.movedTo(transform(EJSON.clone(this.docs.get(id))), from, to, before || null); }, - removed(id) { - const self = this; + removed (id) { if (!(observeCallbacks.removedAt || observeCallbacks.removed)) return; // technically maybe there should be an EJSON.clone here, but it's about - // to be removed from self.docs! - const doc = transform(self.docs.get(id)); + // to be removed from this.docs! + const doc = transform(this.docs.get(id)); if (observeCallbacks.removedAt) { - const index = indices ? self.docs.indexOf(id) : -1; + const index = indices ? this.docs.indexOf(id) : -1; observeCallbacks.removedAt(doc, index); } else { observeCallbacks.removed(doc); @@ -1049,26 +1029,24 @@ LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => { }; } else { observeChangesCallbacks = { - added(id, fields) { + added (id, fields) { if (!suppressed && observeCallbacks.added) { const doc = Object.assign(fields, {_id: id}); observeCallbacks.added(transform(doc)); } }, - changed(id, fields) { - const self = this; + changed (id, fields) { if (observeCallbacks.changed) { - const oldDoc = self.docs.get(id); + const oldDoc = this.docs.get(id); const doc = EJSON.clone(oldDoc); DiffSequence.applyChanges(doc, fields); observeCallbacks.changed(transform(doc), transform(EJSON.clone(oldDoc))); } }, - removed(id) { - const self = this; + removed (id) { if (observeCallbacks.removed) { - observeCallbacks.removed(transform(self.docs.get(id))); + observeCallbacks.removed(transform(this.docs.get(id))); } } }; diff --git a/packages/minimongo/main_server.js b/packages/minimongo/main_server.js index a24849f8a8..7b3fb9992c 100644 --- a/packages/minimongo/main_server.js +++ b/packages/minimongo/main_server.js @@ -7,7 +7,6 @@ import { } from './common.js'; Minimongo._pathsElidingNumericKeys = function (paths) { - const self = this; return paths.map(path => path.split('.').filter(part => !isNumericKey(part)).join('.')); }; @@ -20,11 +19,10 @@ Minimongo._pathsElidingNumericKeys = function (paths) { // - $unset // - 'abc.d': 1 Minimongo.Matcher.prototype.affectedByModifier = function (modifier) { - const self = this; // safe check for $set/$unset being objects modifier = Object.assign({ $set: {}, $unset: {} }, modifier); const modifiedPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset)); - const meaningfulPaths = self._getPaths(); + const meaningfulPaths = this._getPaths(); return modifiedPaths.some(path => { const mod = path.split('.'); @@ -66,17 +64,16 @@ Minimongo.Matcher.prototype.affectedByModifier = function (modifier) { // stay 'false'. // Currently doesn't support $-operators and numeric indices precisely. Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { - const self = this; if (!this.affectedByModifier(modifier)) return false; modifier = Object.assign({$set:{}, $unset:{}}, modifier); const modifierPaths = Object.keys(modifier.$set).concat(Object.keys(modifier.$unset)); - if (!self.isSimple()) + if (!this.isSimple()) return true; - if (self._getPaths().some(pathHasNumericKeys) || + if (this._getPaths().some(pathHasNumericKeys) || modifierPaths.some(pathHasNumericKeys)) return true; @@ -85,8 +82,8 @@ 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. - const expectedScalarIsObject = Object.keys(self._selector).some(path => { - const sel = self._selector[path]; + const expectedScalarIsObject = Object.keys(this._selector).some(path => { + const sel = this._selector[path]; if (! isOperatorObject(sel)) return false; return modifierPaths.some(modifierPath => startsWith(modifierPath, `${path}.`)); @@ -98,7 +95,7 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // See if we can apply the modifier on the ideally matching object. If it // still matches the selector, then the modifier could have turned the real // object in the database into something matching. - const matchingDocument = EJSON.clone(self.matchingDocument()); + const matchingDocument = EJSON.clone(this.matchingDocument()); // The selector is too complex, anything can happen. if (matchingDocument === null) @@ -122,15 +119,14 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { throw e; } - return self.documentMatches(matchingDocument).result; + return this.documentMatches(matchingDocument).result; }; // Knows how to combine a mongo selector and a fields projection to a new fields // projection taking into account active fields from the passed selector. // @returns Object - projection object (same as fields option of mongo cursor) Minimongo.Matcher.prototype.combineIntoProjection = function (projection) { - const self = this; - const selectorPaths = Minimongo._pathsElidingNumericKeys(self._getPaths()); + const selectorPaths = Minimongo._pathsElidingNumericKeys(this._getPaths()); // Special case for $where operator in the selector - projection should depend // on all fields of the document. getSelectorPaths returns a list of paths @@ -147,18 +143,16 @@ Minimongo.Matcher.prototype.combineIntoProjection = function (projection) { // { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } // => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } Minimongo.Matcher.prototype.matchingDocument = function () { - const self = this; - // check if it was computed before - if (self._matchingDocument !== undefined) - return self._matchingDocument; + if (this._matchingDocument !== undefined) + return this._matchingDocument; // If the analysis of this selector is too hard for our implementation // fallback to "YES" let fallback = false; - self._matchingDocument = pathsToTree(self._getPaths(), + this._matchingDocument = pathsToTree(this._getPaths(), path => { - const valueSelector = self._selector[path]; + const valueSelector = this._selector[path]; if (isOperatorObject(valueSelector)) { // if there is a strict equality, there is a good // chance we can use one of those as "matching" @@ -191,7 +185,7 @@ Minimongo.Matcher.prototype.matchingDocument = function () { return middle; } else if (onlyContainsKeys(valueSelector, ['$nin', '$ne'])) { - // Since self._isSimple makes sure $nin and $ne are not combined with + // Since this._isSimple makes sure $nin and $ne are not combined with // objects or arrays, we can confidently return an empty object as it // never matches any scalar. return {}; @@ -199,26 +193,24 @@ Minimongo.Matcher.prototype.matchingDocument = function () { fallback = true; } } - return self._selector[path]; + return this._selector[path]; }, x => x); if (fallback) - self._matchingDocument = null; + this._matchingDocument = null; - return self._matchingDocument; + return this._matchingDocument; }; // Minimongo.Sorter gets a similar method, which delegates to a Matcher it made // for this exact purpose. Minimongo.Sorter.prototype.affectedByModifier = function (modifier) { - const self = this; - return self._selectorForAffectedByModifier.affectedByModifier(modifier); + return this._selectorForAffectedByModifier.affectedByModifier(modifier); }; Minimongo.Sorter.prototype.combineIntoProjection = function (projection) { - const self = this; - const specPaths = Minimongo._pathsElidingNumericKeys(self._getPaths()); + const specPaths = Minimongo._pathsElidingNumericKeys(this._getPaths()); return combineImportantPathsIntoProjection(specPaths, projection); }; diff --git a/packages/minimongo/sorter.js b/packages/minimongo/sorter.js index a332a5bfaa..5a4ba04061 100644 --- a/packages/minimongo/sorter.js +++ b/packages/minimongo/sorter.js @@ -21,19 +21,16 @@ import { // first, or 0 if neither object comes before the other. export class Sorter { - constructor (spec, options) { - const self = this; - options = options || {}; - - self._sortSpecParts = []; - self._sortFunction = null; + constructor (spec, options = {}) { + this._sortSpecParts = []; + this._sortFunction = null; const addSpecPart = (path, ascending) => { if (!path) throw Error("sort keys must be non-empty"); if (path.charAt(0) === '$') throw Error(`unsupported sort key: ${path}`); - self._sortSpecParts.push({ + this._sortSpecParts.push({ path, lookup: makeLookupFunction(path, {forSort: true}), ascending @@ -54,46 +51,44 @@ export class Sorter { addSpecPart(key, value >= 0); }); } else if (typeof spec === "function") { - self._sortFunction = spec; + this._sortFunction = spec; } else { throw Error(`Bad sort specification: ${JSON.stringify(spec)}`); } // If a function is specified for sorting, we skip the rest. - if (self._sortFunction) + if (this._sortFunction) return; // To implement affectedByModifier, we piggy-back on top of Matcher's // affectedByModifier code; we create a selector that is affected by the same // modifiers as this sort order. This is only implemented on the server. - if (self.affectedByModifier) { + if (this.affectedByModifier) { const selector = {}; - self._sortSpecParts.forEach(spec => { + this._sortSpecParts.forEach(spec => { selector[spec.path] = 1; }); - self._selectorForAffectedByModifier = new Minimongo.Matcher(selector); + this._selectorForAffectedByModifier = new Minimongo.Matcher(selector); } - self._keyComparator = composeComparators( - self._sortSpecParts.map((spec, i) => self._keyFieldComparator(i))); + this._keyComparator = composeComparators( + this._sortSpecParts.map((spec, i) => this._keyFieldComparator(i))); // If you specify a matcher for this Sorter, _keyFilter may be set to a // function which selects whether or not a given "sort key" (tuple of values // for the different sort spec fields) is compatible with the selector. - self._keyFilter = null; - options.matcher && self._useWithMatcher(options.matcher); + this._keyFilter = null; + options.matcher && this._useWithMatcher(options.matcher); } getComparator (options) { - const self = this; - // 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 // sort effectively overrides $near - if (self._sortSpecParts.length || !options || !options.distances) { - return self._getBaseComparator(); + if (this._sortSpecParts.length || !options || !options.distances) { + return this._getBaseComparator(); } const distances = options.distances; @@ -112,21 +107,18 @@ export class Sorter { // parts. Returns negative, 0, or positive based on using the sort spec to // compare fields. _compareKeys (key1, key2) { - const self = this; - if (key1.length !== self._sortSpecParts.length || - key2.length !== self._sortSpecParts.length) { + if (key1.length !== this._sortSpecParts.length || + key2.length !== this._sortSpecParts.length) { throw Error("Key has wrong length"); } - return self._keyComparator(key1, key2); + return this._keyComparator(key1, key2); } // Iterates over each possible "key" from doc (ie, over each branch), calling // 'cb' with the key. _generateKeysFromDoc (doc, cb) { - const self = this; - - if (self._sortSpecParts.length === 0) + if (this._sortSpecParts.length === 0) throw new Error("can't generate keys without a spec"); // maps index -> ({'' -> value} or {path -> value}) @@ -136,7 +128,7 @@ export class Sorter { let knownPaths = null; - self._sortSpecParts.forEach((spec, whichField) => { + this._sortSpecParts.forEach((spec, whichField) => { // Expand any leaf arrays that we find, and ignore those arrays // themselves. (We never sort based on an array itself.) let branches = expandArraysInBranches(spec.lookup(doc), true); @@ -221,21 +213,19 @@ export class Sorter { // Returns a comparator that represents the sort specification (but not // including a possible geoquery distance tie-breaker). _getBaseComparator () { - const self = this; - - if (self._sortFunction) - return self._sortFunction; + if (this._sortFunction) + return this._sortFunction; // If we're only sorting on geoquery distance and no specs, just say // everything is equal. - if (!self._sortSpecParts.length) { + if (!this._sortSpecParts.length) { return (doc1, doc2) => 0; } return (doc1, doc2) => { - const key1 = self._getMinKeyFromDoc(doc1); - const key2 = self._getMinKeyFromDoc(doc2); - return self._compareKeys(key1, key2); + const key1 = this._getMinKeyFromDoc(doc1); + const key2 = this._getMinKeyFromDoc(doc2); + return this._compareKeys(key1, key2); }; } @@ -250,18 +240,17 @@ export class Sorter { // 1, y: 3}]} with sort spec {'a.x': 1, 'a.y': 1}, the only keys are [0,5] and // [1,3], and the minimum key is [0,5]; notably, [0,3] is NOT a key. _getMinKeyFromDoc (doc) { - const self = this; let minKey = null; - self._generateKeysFromDoc(doc, key => { - if (!self._keyCompatibleWithSelector(key)) + this._generateKeysFromDoc(doc, key => { + if (!this._keyCompatibleWithSelector(key)) return; if (minKey === null) { minKey = key; return; } - if (self._compareKeys(key, minKey) < 0) { + if (this._compareKeys(key, minKey) < 0) { minKey = key; } }); @@ -274,20 +263,17 @@ export class Sorter { } _getPaths () { - const self = this; - return self._sortSpecParts.map(part => part.path); + return this._sortSpecParts.map(part => part.path); } _keyCompatibleWithSelector (key) { - const self = this; - return !self._keyFilter || self._keyFilter(key); + return !this._keyFilter || this._keyFilter(key); } // Given an index 'i', returns a comparator that compares two key arrays based // on field 'i'. _keyFieldComparator (i) { - const self = this; - const invert = !self._sortSpecParts[i].ascending; + const invert = !this._sortSpecParts[i].ascending; return (key1, key2) => { let compare = LocalCollection._f._cmp(key1[i], key2[i]); if (invert) @@ -316,15 +302,13 @@ export class Sorter { // subtle and undocumented; we've gotten as close as we can figure out based // on our understanding of Mongo's behavior. _useWithMatcher (matcher) { - const self = this; - - if (self._keyFilter) + if (this._keyFilter) throw Error("called _useWithMatcher twice?"); // 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 (!self._sortSpecParts.length) + if (!this._sortSpecParts.length) return; const selector = matcher._selector; @@ -335,7 +319,7 @@ export class Sorter { return; const constraintsByPath = {}; - self._sortSpecParts.forEach((spec, i) => { + this._sortSpecParts.forEach((spec, i) => { constraintsByPath[spec.path] = []; }); @@ -394,10 +378,10 @@ export class Sorter { // 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 (!constraintsByPath[self._sortSpecParts[0].path].length) + if (!constraintsByPath[this._sortSpecParts[0].path].length) return; - self._keyFilter = key => self._sortSpecParts.every((specPart, index) => constraintsByPath[specPart.path].every(f => f(key[index]))); + this._keyFilter = key => this._sortSpecParts.every((specPart, index) => constraintsByPath[specPart.path].every(f => f(key[index]))); } }