diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b74300e..ed3e347 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,12 @@ permissions: jobs: test: # os is not in the matrix, because otherwise there would be way too many testing instances - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - node: [14, 16, 18] - mongo: [4.2, 5.0] + node: ['lts/*', 'lts/-1', 'latest'] + mongo: [7.0, 8.0] services: mongodb: image: mongo:${{ matrix.mongo }} diff --git a/lib/collection/collection.js b/lib/collection/collection.js index b473f89..10366ca 100644 --- a/lib/collection/collection.js +++ b/lib/collection/collection.js @@ -10,9 +10,11 @@ const methods = [ 'updateMany', 'updateOne', 'replaceOne', - 'count', + 'countDocuments', + 'estimatedDocumentCount', 'distinct', 'findOneAndDelete', + 'findOneAndReplace', 'findOneAndUpdate', 'aggregate', 'findCursor', diff --git a/lib/collection/node.js b/lib/collection/node.js index 6580f51..d12983c 100644 --- a/lib/collection/node.js +++ b/lib/collection/node.js @@ -31,10 +31,17 @@ class NodeCollection extends Collection { } /** - * count(match, options) + * countDocuments(match, options) */ - async count(match, options) { - return this.collection.count(match, options); + async countDocuments(match, options) { + return this.collection.countDocuments(match, options); + } + + /** + * estimatedDocumentCount(match, options) + */ + async estimatedDocumentCount(match, options) { + return this.collection.estimatedDocumentCount(match, options); } /** @@ -93,6 +100,13 @@ class NodeCollection extends Collection { return this.collection.findOneAndUpdate(match, update, options); } + /** + * findOneAndReplace(match, update, options) + */ + async findOneAndReplace(match, update, options) { + return this.collection.findOneAndReplace(match, update, options); + } + /** * var cursor = findCursor(match, options) */ diff --git a/lib/mquery.js b/lib/mquery.js index dc874ad..13aa678 100644 --- a/lib/mquery.js +++ b/lib/mquery.js @@ -7,7 +7,6 @@ const assert = require('assert'); const util = require('util'); const utils = require('./utils'); -const debug = require('debug')('mquery'); /** * Query constructor used for building queries. @@ -59,29 +58,6 @@ function Query(criteria, options) { } } -/** - * This is a parameter that the user can set which determines if mquery - * uses $within or $geoWithin for queries. It defaults to true which - * means $geoWithin will be used. If using MongoDB < 2.4 you should - * set this to false. - * - * @api public - * @property use$geoWithin - */ - -let $withinCmd = '$geoWithin'; -Object.defineProperty(Query, 'use$geoWithin', { - get: function() { return $withinCmd == '$geoWithin'; }, - set: function(v) { - if (true === v) { - // mongodb >= 2.4 - $withinCmd = '$geoWithin'; - } else { - $withinCmd = '$within'; - } - } -}); - /** * Converts this query to a constructor function with all arguments and options retained. * @@ -733,7 +709,7 @@ Query.prototype.elemMatch = function() { Query.prototype.within = function within() { // opinionated, must be used after where this._ensurePath('within'); - this._geoComparison = $withinCmd; + this._geoComparison = '$geoWithin'; if (0 === arguments.length) { return this; @@ -801,7 +777,7 @@ Query.prototype.box = function() { } const conds = this._conditions[path] || (this._conditions[path] = {}); - conds[this._geoComparison || $withinCmd] = { $box: box }; + conds[this._geoComparison || '$geoWithin'] = { $box: box }; return this; }; @@ -835,7 +811,7 @@ Query.prototype.polygon = function() { } const conds = this._conditions[path] || (this._conditions[path] = {}); - conds[this._geoComparison || $withinCmd] = { $polygon: val }; + conds[this._geoComparison || '$geoWithin'] = { $polygon: val }; return this; }; @@ -883,7 +859,7 @@ Query.prototype.circle = function() { ? '$centerSphere' : '$center'; - const wKey = this._geoComparison || $withinCmd; + const wKey = this._geoComparison || '$geoWithin'; conds[wKey] = {}; conds[wKey][type] = [val.center, val.radius]; @@ -1857,8 +1833,6 @@ Query.prototype._find = async function _find() { options.fields = this._fieldsForExec(); } - debug('_find', this._collection.collectionName, conds, options); - return this._collection.find(conds, options); }; @@ -1893,7 +1867,6 @@ Query.prototype.cursor = function cursor(criteria) { options.fields = this._fieldsForExec(); } - debug('findCursor', this._collection.collectionName, conds, options); return this._collection.findCursor(conds, options); }; @@ -1937,53 +1910,79 @@ Query.prototype._findOne = async function _findOne() { options.fields = this._fieldsForExec(); } - debug('findOne', this._collection.collectionName, conds, options); - return this._collection.findOne(conds, options); }; /** - * Exectues the query as a count() operation. + * Executes the query as a countDocuments() operation. * * #### Example: * - * query.count().where('color', 'black').exec(); + * query.countDocuments().where('color', 'black').exec(); * - * query.count({ color: 'black' }) + * query.countDocuments({ color: 'black' }) * - * await query.count({ color: 'black' }); + * await query.countDocuments({ color: 'black' }); * - * const doc = await query.where('color', 'black').count(); + * const count = await query.where('color', 'black').countDocuments(); * console.log('there are %d kittens', count); * - * @param {Object} [criteria] mongodb selector + * @param {Object} [filter] mongodb selector * @return {Query} this - * @see mongodb http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Count * @api public */ -Query.prototype.count = function(criteria) { - this.op = 'count'; +Query.prototype.countDocuments = function(filter) { + this.op = 'countDocuments'; this._validate(); - if (Query.canMerge(criteria)) { - this.merge(criteria); + if (Query.canMerge(filter)) { + this.merge(filter); } return this; }; /** - * Executes a `count` Query + * Executes a `countDocuments` Query * @returns the results */ -Query.prototype._count = async function _count() { - const conds = this._conditions, - options = this._optionsForExec(); +Query.prototype._countDocuments = async function _countDocuments() { + const conds = this._conditions; + const options = this._optionsForExec(); - debug('count', this._collection.collectionName, conds, options); + return this._collection.countDocuments(conds, options); +}; - return this._collection.count(conds, options); +/** + * Executes the query as a estimatedDocumentCount() operation. + * + * #### Example: + * + * query.estimatedDocumentCount(); + * + * const count = await query.estimatedDocumentCount(); + * console.log('there are %d kittens', count); + * + * @return {Query} this + * @api public + */ + +Query.prototype.estimatedDocumentCount = function() { + this.op = 'estimatedDocumentCount'; + this._validate(); + + return this; +}; + +/** + * Executes a `count` Query + * @returns the results + */ +Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount() { + const conds = this._conditions; + const options = this._optionsForExec(); + return this._collection.estimatedDocumentCount(conds, options); }; /** @@ -2037,8 +2036,6 @@ Query.prototype._distinct = async function _distinct() { const conds = this._conditions, options = this._optionsForExec(); - debug('distinct', this._collection.collectionName, conds, options); - return this._collection.distinct(this._distinctDoc, conds, options); }; @@ -2061,11 +2058,6 @@ Query.prototype._distinct = async function _distinct() { */ Query.prototype.updateMany = function updateMany(criteria, doc, options) { - if (arguments.length === 1) { - doc = criteria; - criteria = options = undefined; - } - return _update(this, 'updateMany', criteria, doc, options); }; @@ -2096,11 +2088,6 @@ Query.prototype._updateMany = async function() { */ Query.prototype.updateOne = function updateOne(criteria, doc, options) { - if (arguments.length === 1) { - doc = criteria; - criteria = options = undefined; - } - return _update(this, 'updateOne', criteria, doc, options); }; @@ -2131,11 +2118,6 @@ Query.prototype._updateOne = async function() { */ Query.prototype.replaceOne = function replaceOne(criteria, doc, options) { - if (arguments.length === 1) { - doc = criteria; - criteria = options = undefined; - } - this.setOptions({ overwrite: true }); return _update(this, 'replaceOne', criteria, doc, options); }; @@ -2183,8 +2165,6 @@ async function _updateExec(query, op) { const criteria = query._conditions; const doc = query._updateForExec(); - debug('update', query._collection.collectionName, criteria, doc, options); - return query._collection[op](criteria, doc, options); } @@ -2220,8 +2200,6 @@ Query.prototype._deleteOne = async function() { const conds = this._conditions; - debug('deleteOne', this._collection.collectionName, conds, options); - return this._collection.deleteOne(conds, options); }; @@ -2258,13 +2236,11 @@ Query.prototype._deleteMany = async function() { const conds = this._conditions; - debug('deleteOne', this._collection.collectionName, conds, options); - return this._collection.deleteMany(conds, options); }; /** - * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) update command. + * Issues a mongodb findOneAndUpdate command. * * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any). * @@ -2297,11 +2273,6 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options) { this.op = 'findOneAndUpdate'; this._validate(); - if (arguments.length === 1) { - doc = criteria; - criteria = options = undefined; - } - if (Query.canMerge(criteria)) { this.merge(criteria); } @@ -2329,7 +2300,69 @@ Query.prototype._findOneAndUpdate = async function() { }; /** - * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command. + * Issues a mongodb findOneAndReplace command. + * + * Finds a matching document, replaces it according to the `replacement` arg, passing any `options`, and returns the found document (if any). + * + * #### Available options + * + * - `new`: bool - true to return the modified document rather than the original. defaults to true + * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * + * #### Examples: + * + * await query.findOneAndReplace(conditions, replacement, options) // executes + * query.findOneAndReplace(conditions, replacement, options) // returns Query + * await query.findOneAndReplace(conditions, replacement) // executes + * query.findOneAndReplace(conditions, replacement) // returns Query + * await query.findOneAndReplace(replacement) // returns Query + * query.findOneAndReplace(replacement) // returns Query + * await query.findOneAndReplace() // executes + * query.findOneAndReplace() // returns Query + * + * @param {Object|Query} [query] + * @param {Object} [replacement] + * @param {Object} [options] + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @return {Query} this + * @api public + */ + +Query.prototype.findOneAndReplace = function(criteria, replacement, options) { + this.op = 'findOneAndReplace'; + this._validate(); + + if (Query.canMerge(criteria)) { + this.merge(criteria); + } + + // apply replacement + if (replacement) { + this._updateDoc = replacement; + this.options = this.options || {}; + this.options.overwrite = true; + } + + options && this.setOptions(options); + + return this; +}; + +/** + * Executes a `findOneAndReplace` Query + * @returns the results + */ +Query.prototype._findOneAndReplace = async function() { + const conds = this._conditions; + const replacement = this._updateForExec(); + const options = this._optionsForExec(); + + return this._collection.findOneAndReplace(conds, replacement, options); +}; + +/** + * Issues a mongodb findOneAndDelete. * * Finds a matching document, removes it, returning the found document (if any). * @@ -2339,28 +2372,25 @@ Query.prototype._findOneAndUpdate = async function() { * * #### Examples: * - * await A.where().findOneAndRemove(conditions, options) // executes - * A.where().findOneAndRemove(conditions, options) // return Query - * await A.where().findOneAndRemove(conditions) // executes - * A.where().findOneAndRemove(conditions) // returns Query - * await A.where().findOneAndRemove() // executes - * A.where().findOneAndRemove() // returns Query - * A.where().findOneAndDelete() // alias of .findOneAndRemove() + * await A.where().findOneAndDelete(conditions, options) // executes + * A.where().findOneAndDelete(conditions, options) // return Query + * await A.where().findOneAndDelete(conditions) // executes + * A.where().findOneAndDelete(conditions) // returns Query + * await A.where().findOneAndDelete() // executes + * A.where().findOneAndDelete() // returns Query * - * @param {Object} [conditions] + * @param {Object} [filter] * @param {Object} [options] * @return {Query} this - * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command * @api public */ -Query.prototype.findOneAndRemove = Query.prototype.findOneAndDelete = function(conditions, options) { - this.op = 'findOneAndRemove'; +Query.prototype.findOneAndDelete = function(filter, options) { + this.op = 'findOneAndDelete'; this._validate(); - // apply conditions - if (Query.canMerge(conditions)) { - this.merge(conditions); + if (Query.canMerge(filter)) { + this.merge(filter); } // apply options @@ -2373,7 +2403,7 @@ Query.prototype.findOneAndRemove = Query.prototype.findOneAndDelete = function(c * Executes a `findOneAndRemove` Query * @returns the results */ -Query.prototype._findOneAndRemove = async function() { +Query.prototype._findOneAndDelete = async function() { const options = this._optionsForExec(); const conds = this._conditions; @@ -2469,8 +2499,6 @@ Query.prototype.cursor = function() { options.fields = this._fieldsForExec(); } - debug('cursor', this._collection.collectionName, conds, options); - return this._collection.findCursor(conds, options); }; @@ -2587,7 +2615,7 @@ Query.prototype._fieldsForExec = function() { */ Query.prototype._updateForExec = function() { - const update = utils.clone(this._updateDoc); + const update = this._updateDoc == null ? {} : utils.clone(this._updateDoc); const ops = utils.keys(update); const ret = {}; diff --git a/lib/permissions.js b/lib/permissions.js index 97d2c73..b7ea0f3 100644 --- a/lib/permissions.js +++ b/lib/permissions.js @@ -34,7 +34,7 @@ denied.distinct.tailable = true; denied.findOneAndUpdate = -denied.findOneAndRemove = function(self) { +denied.findOneAndDelete = function(self) { const keys = Object.keys(denied.findOneAndUpdate); let err; @@ -53,13 +53,28 @@ denied.findOneAndUpdate.skip = denied.findOneAndUpdate.batchSize = denied.findOneAndUpdate.tailable = true; +denied.findOneAndReplace = function(self) { + const keys = Object.keys(denied.findOneAndUpdate); + let err; + + keys.every(function(option) { + if (self.options[option]) { + err = option; + return false; + } + return true; + }); + + return err; +}; + -denied.count = function(self) { +denied.countDocuments = function(self) { if (self._fields && Object.keys(self._fields).length > 0) { return 'field selection and slice'; } - const keys = Object.keys(denied.count); + const keys = Object.keys(denied.countDocuments); let err; keys.every(function(option) { @@ -73,6 +88,6 @@ denied.count = function(self) { return err; }; -denied.count.slice = -denied.count.batchSize = -denied.count.tailable = true; +denied.countDocuments.slice = +denied.countDocuments.batchSize = +denied.countDocuments.tailable = true; diff --git a/package.json b/package.json index 9016083..ea4d744 100644 --- a/package.json +++ b/package.json @@ -13,16 +13,13 @@ "url": "git://github.com/aheckmann/mquery.git" }, "engines": { - "node": ">=14.0.0" - }, - "dependencies": { - "debug": "4.x" + "node": ">=16.0.0" }, "devDependencies": { "eslint": "8.x", "eslint-plugin-mocha-no-only": "1.1.1", - "mocha": "9.x", - "mongodb": "5.x" + "mocha": "11.x", + "mongodb": "6.x" }, "bugs": { "url": "https://github.com/aheckmann/mquery/issues/new" diff --git a/test/index.js b/test/index.js index 2da006b..c49c7d0 100644 --- a/test/index.js +++ b/test/index.js @@ -59,7 +59,7 @@ describe('mquery', function() { const q = mquery().setOptions(opts); q.where(match); q.select(select); - q.updateOne(update); + q.updateOne({}, update); q.where(path); q.find(); @@ -546,33 +546,33 @@ describe('mquery', function() { describe('of length 1', function() { it('delegates to circle when center exists', function() { const m = mquery().where('loc').within({ center: [10, 10], radius: 3 }); - assert.deepEqual({ $within: { $center: [[10, 10], 3] } }, m._conditions.loc); + assert.deepEqual({ $geoWithin: { $center: [[10, 10], 3] } }, m._conditions.loc); }); it('delegates to box when exists', function() { const m = mquery().where('loc').within({ box: [[10, 10], [11, 14]] }); - assert.deepEqual({ $within: { $box: [[10, 10], [11, 14]] } }, m._conditions.loc); + assert.deepEqual({ $geoWithin: { $box: [[10, 10], [11, 14]] } }, m._conditions.loc); }); it('delegates to polygon when exists', function() { const m = mquery().where('loc').within({ polygon: [[10, 10], [11, 14], [10, 9]] }); - assert.deepEqual({ $within: { $polygon: [[10, 10], [11, 14], [10, 9]] } }, m._conditions.loc); + assert.deepEqual({ $geoWithin: { $polygon: [[10, 10], [11, 14], [10, 9]] } }, m._conditions.loc); }); it('delegates to geometry when exists', function() { const m = mquery().where('loc').within({ type: 'Polygon', coordinates: [[10, 10], [11, 14], [10, 9]] }); - assert.deepEqual({ $within: { $geometry: { type: 'Polygon', coordinates: [[10, 10], [11, 14], [10, 9]] } } }, m._conditions.loc); + assert.deepEqual({ $geoWithin: { $geometry: { type: 'Polygon', coordinates: [[10, 10], [11, 14], [10, 9]] } } }, m._conditions.loc); }); }); describe('of length 2', function() { it('delegates to box()', function() { const m = mquery().where('loc').within([1, 2], [2, 5]); - assert.deepEqual(m._conditions.loc, { $within: { $box: [[1, 2], [2, 5]] } }); + assert.deepEqual(m._conditions.loc, { $geoWithin: { $box: [[1, 2], [2, 5]] } }); }); }); describe('of length > 2', function() { it('delegates to polygon()', function() { const m = mquery().where('loc').within([1, 2], [2, 5], [2, 4], [1, 3]); - assert.deepEqual(m._conditions.loc, { $within: { $polygon: [[1, 2], [2, 5], [2, 4], [1, 3]] } }); + assert.deepEqual(m._conditions.loc, { $geoWithin: { $polygon: [[1, 2], [2, 5], [2, 4], [1, 3]] } }); }); }); }); @@ -1105,7 +1105,7 @@ describe('mquery', function() { }); noDistinct('slice'); - no('count', 'slice'); + no('countDocuments', 'slice'); }); // options @@ -1202,7 +1202,7 @@ describe('mquery', function() { }); if (!options.distinct) noDistinct(type); - if (!options.count) no('count', type); + if (!options.count) no('countDocuments', type); }); } @@ -1333,7 +1333,7 @@ describe('mquery', function() { assert.equal(m, m.tailable()); }); noDistinct('tailable'); - no('count', 'tailable'); + no('countDocuments', 'tailable'); }); describe('writeConcern', function() { @@ -1408,7 +1408,7 @@ describe('mquery', function() { const original = { $set: { iTerm: true } }; const m = mquery().updateOne(original); const n = mquery().merge(m); - m.updateOne({ $set: { x: 2 } }); + m.updateOne({}, { $set: { x: 2 } }); assert.notDeepEqual(m._updateDoc, n._updateDoc); done(); }); @@ -1429,7 +1429,7 @@ describe('mquery', function() { const original = { $set: { iTerm: true } }; const m = mquery().updateOne(original); const n = mquery().merge(original); - m.updateOne({ $set: { x: 2 } }); + m.updateOne({}, { $set: { x: 2 } }); assert.notDeepEqual(m._updateDoc, n._updateDoc); done(); }); @@ -1557,32 +1557,32 @@ describe('mquery', function() { }); }); - describe('count', function() { + describe('countDocuments', function() { describe('with no exec', function() { it('does not execute', function() { const m = mquery(); assert.doesNotThrow(function() { - m.count(); + m.countDocuments(); }); assert.doesNotThrow(function() { - m.count({ x: 1 }); + m.countDocuments({ x: 1 }); }); }); }); it('is chainable', function() { const m = mquery(); - const n = m.count({ x: 1 }).count().count({ y: 2 }); + const n = m.countDocuments({ x: 1 }).countDocuments().countDocuments({ y: 2 }); assert.equal(m, n); assert.deepEqual(m._conditions, { x: 1, y: 2 }); - assert.equal('count', m.op); + assert.equal(m.op, 'countDocuments'); }); it('merges other queries', function() { const m = mquery({ name: 'mquery' }); m.read('nearest'); m.select('_id'); - const a = mquery().count(m); + const a = mquery().countDocuments(m); assert.deepEqual(a._conditions, m._conditions); assert.deepEqual(a.options, m.options); assert.deepEqual(a._fields, m._fields); @@ -1598,18 +1598,18 @@ describe('mquery', function() { }); it('when criteria is passed with a exec', async() => { - const count = await mquery().collection(col).count({ name: 'mquery count' }); + const count = await mquery().collection(col).countDocuments({ name: 'mquery count' }); assert.ok(count); assert.ok(1 === count); }); it('when Query is passed with a exec', async() => { const m = mquery({ name: 'mquery count' }); - const count = await mquery().collection(col).count(m); + const count = await mquery().collection(col).countDocuments(m); assert.ok(count); assert.ok(1 === count); }); it('when just nothing is passed but executed', async() => { - const count = await mquery({ name: 'mquery count' }).collection(col).count(); + const count = await mquery({ name: 'mquery count' }).collection(col).countDocuments(); assert.ok(1 === count); }); }); @@ -1617,49 +1617,49 @@ describe('mquery', function() { describe('validates its option', function() { it('sort', function(done) { assert.doesNotThrow(function() { - mquery().sort('x').count(); + mquery().sort('x').countDocuments(); }); done(); }); it('select', function(done) { assert.throws(function() { - mquery().select('x').count(); + mquery().select('x').countDocuments(); }, /field selection and slice cannot be used with count/); done(); }); it('slice', function(done) { assert.throws(function() { - mquery().where('x').slice(-3).count(); + mquery().where('x').slice(-3).countDocuments(); }, /field selection and slice cannot be used with count/); done(); }); it('limit', function(done) { assert.doesNotThrow(function() { - mquery().limit(3).count(); + mquery().limit(3).countDocuments(); }); done(); }); it('skip', function(done) { assert.doesNotThrow(function() { - mquery().skip(3).count(); + mquery().skip(3).countDocuments(); }); done(); }); it('batchSize', function(done) { assert.throws(function() { - mquery({}, { batchSize: 3 }).count(); + mquery({}, { batchSize: 3 }).countDocuments(); }, /batchSize cannot be used with count/); done(); }); it('tailable', function(done) { assert.throws(function() { - mquery().tailable().count(); + mquery().tailable().countDocuments(); }, /tailable cannot be used with count/); done(); }); @@ -1845,7 +1845,7 @@ describe('mquery', function() { }); it('is chainable', function() { - const m = mquery({ x: 1 }).updateOne({ y: 2 }); + const m = mquery({ x: 1 }).updateOne(null, { y: 2 }); const n = m.where({ y: 2 }); assert.equal(m, n); assert.deepEqual(n._conditions, { x: 1, y: 2 }); @@ -1855,8 +1855,8 @@ describe('mquery', function() { it('merges update doc arg', function() { const a = [1, 2]; - const m = mquery().where({ name: 'mquery' }).updateOne({ x: 'stuff', a: a }); - m.updateOne({ z: 'stuff' }); + const m = mquery().where({ name: 'mquery' }).updateOne(null, { x: 'stuff', a: a }); + m.updateOne(null, { z: 'stuff' }); assert.deepEqual(m._updateDoc, { z: 'stuff', x: 'stuff', a: a }); assert.deepEqual(m._conditions, { name: 'mquery' }); assert.ok(!m.options.overwrite); @@ -1904,7 +1904,7 @@ describe('mquery', function() { it('works', async() => { const m = mquery().collection(col); - const num = await m.where({ _id: id }).updateOne({ name: 'changed' }); + const num = await m.where({ _id: id }).updateOne(null, { name: 'changed' }); assert.ok(1, num); const doc = await m.findOne(); assert.equal(doc.name, 'changed'); @@ -1914,7 +1914,7 @@ describe('mquery', function() { describe('when just exec passed', function() { it('works', async() => { const m = mquery().collection(col).where({ _id: id }); - m.updateOne({ name: 'Frankenweenie' }); + m.updateOne(null, { name: 'Frankenweenie' }); const res = await m.updateOne(); assert.equal(res.modifiedCount, 1); const doc = await m.findOne(); @@ -1988,6 +1988,14 @@ describe('mquery', function() { validateFindAndModifyOptions('findOneAndUpdate'); + beforeEach(function() { + return mquery().collection(col).updateOne({ name }, { name }, { upsert: true }); + }); + + afterEach(function () { + return mquery().collection(col).deleteMany(); + }); + describe('with 0 args', function() { it('makes no changes', function() { const m = mquery(); @@ -1997,28 +2005,20 @@ describe('mquery', function() { }); describe('with 1 arg', function() { describe('that is an object', function() { - it('updates the doc', function() { + it('sets conditions', function() { const m = mquery(); - const n = m.findOneAndUpdate({ $set: { name: '1 arg' } }); - assert.deepEqual(n._updateDoc, { $set: { name: '1 arg' } }); + const n = m.findOneAndUpdate({ name: '1 arg' }); + assert.deepEqual(n._conditions, { name: '1 arg' }); + assert.strictEqual(n._updateDoc, undefined); }); }); describe('that is a query', function() { it('updates the doc', function() { - const m = mquery({ name: name }).updateOne({ x: 1 }); + const m = mquery({ name: name }).updateOne(null, { x: 1 }); const n = mquery().findOneAndUpdate(m); assert.deepEqual(n._updateDoc, { x: 1 }); }); }); - it('that is a function', async() => { - await col.insertOne({ name: name }); - const m = mquery({ name: name }).collection(col); - name = '1 arg'; - const n = m.updateOne({ $set: { name: name } }).setOptions({ returnDocument: 'after' }); - const res = await n.findOneAndUpdate(); - assert.ok(res.value); - assert.equal(res.value.name, name); - }); }); describe('with 2 args', function() { it('conditions + update', function() { @@ -2036,7 +2036,7 @@ describe('mquery', function() { }); it('update + exec', async() => { const m = mquery().collection(col).where({ name: name }); - const res = await m.findOneAndUpdate({}, { $inc: { age: 10 } }, { returnDocument: 'after' }); + const res = await m.findOneAndUpdate({}, { $set: { age: 10 } }, { returnDocument: 'after', includeResultMetadata: true }); assert.equal(10, res.value.age); }); }); @@ -2050,32 +2050,120 @@ describe('mquery', function() { }); it('conditions + update + exec', async() => { const m = mquery().collection(col); - const res = await m.findOneAndUpdate({ name: name }, { works: true }, { returnDocument: 'after' }); + const res = await m.findOneAndUpdate({ name: name }, { works: true }, { returnDocument: 'after', includeResultMetadata: true }); assert.ok(res.value); assert.equal(name, res.value.name); assert.ok(true === res.value.works); }); + it('empty options', async() => { + const m = mquery().collection(col); + const res = await m.findOneAndUpdate({ name: name }, { works: false }, { returnDocument: 'after' }); + assert.ok(res); + assert.equal(name, res.name); + assert.strictEqual(res.works, false); + }); }); - describe('with 4 args', function() { - it('conditions + update + options + exec', async() => { + }); + + describe('findOneAndReplace', function() { + let name = 'findOneAndReplace + fn'; + + validateFindAndModifyOptions('findOneAndReplace'); + + beforeEach(function() { + return mquery().collection(col).updateOne({ name }, { name }, { upsert: true }); + }); + + afterEach(function () { + return mquery().collection(col).deleteMany(); + }); + + describe('with 0 args', function() { + it('makes no changes', function() { + const m = mquery(); + const n = m.findOneAndReplace(); + assert.deepEqual(m, n); + }); + }); + + describe('with 1 arg', function() { + describe('that is an object', function() { + it('replaces the doc', function() { + const m = mquery(); + const n = m.findOneAndReplace({ name: '1 arg', age: 10 }); + assert.deepStrictEqual(n._conditions, { name: '1 arg', age: 10 }); + assert.strictEqual(n._updateDoc, undefined); + }); + }); + describe('that is a query', function() { + it('replaces the doc', function() { + const m = mquery({ name: name }).updateOne(null, { x: 1 }); + const n = mquery().findOneAndReplace(m); + assert.deepEqual(n._updateDoc, { x: 1 }); + }); + }); + }); + + describe('with 2 args', function() { + it('conditions + replacement', function() { const m = mquery().collection(col); - const res = await m.findOneAndUpdate({ name: name }, { works: false }, {}); + m.findOneAndReplace({ name: name }, { name: 'replaced', age: 100 }); + assert.deepEqual({ name: name }, m._conditions); + assert.deepEqual({ name: 'replaced', age: 100 }, m._updateDoc); + }); + it('query + replacement', function() { + const n = mquery({ name: name }); + const m = mquery().collection(col); + m.findOneAndReplace(n, { name: 'replaced', age: 100 }); + assert.deepEqual({ name: name }, m._conditions); + assert.deepEqual({ name: 'replaced', age: 100 }, m._updateDoc); + }); + it('replacement + exec', async() => { + await col.insertOne({ name: name }); + const m = mquery().collection(col).where({ name: name }); + const res = await m.findOneAndReplace({}, { name: 'replaced', age: 101 }, { returnDocument: 'after', includeResultMetadata: true }); assert.ok(res.value); - assert.equal(name, res.value.name); + assert.equal(res.value.name, 'replaced'); + assert.equal(res.value.age, 101); + }); + }); + + describe('with 3 args', function() { + it('conditions + replacement + options', function() { + const m = mquery().collection(col); + const n = m.findOneAndReplace({ name: name }, { name: 'replaced', works: true }, { returnDocument: 'before' }); + assert.deepEqual({ name: name }, n._conditions); + assert.deepEqual({ name: 'replaced', works: true }, n._updateDoc); + assert.deepEqual({ returnDocument: 'before', overwrite: true }, n.options); + }); + it('conditions + replacement + exec', async() => { + await col.insertOne({ name: name }); + const m = mquery().collection(col); + const res = await m.findOneAndReplace({ name: name }, { name: 'replaced', works: true }, { returnDocument: 'after', includeResultMetadata: true }); + assert.ok(res.value); + assert.equal(res.value.name, 'replaced'); assert.ok(true === res.value.works); }); + it('empty options', async() => { + await col.insertOne({ name: name }); + const m = mquery().collection(col); + const res = await m.findOneAndReplace({ name: name }, { name: 'replaced', works: false }, { returnDocument: 'after' }); + assert.ok(res); + assert.equal(res.name, 'replaced'); + assert.ok(false === res.works); + }); }); }); - describe('findOneAndRemove', function() { - let name = 'findOneAndRemove'; + describe('findOneAndDelete', function() { + let name = 'findOneAndDelete'; - validateFindAndModifyOptions('findOneAndRemove'); + validateFindAndModifyOptions('findOneAndDelete'); describe('with 0 args', function() { it('makes no changes', function() { const m = mquery(); - const n = m.findOneAndRemove(); + const n = m.findOneAndDelete(); assert.deepEqual(m, n); }); }); @@ -2083,59 +2171,57 @@ describe('mquery', function() { describe('that is an object', function() { it('updates the doc', function() { const m = mquery(); - const n = m.findOneAndRemove({ name: '1 arg' }); + const n = m.findOneAndDelete({ name: '1 arg' }); assert.deepEqual(n._conditions, { name: '1 arg' }); }); }); describe('that is a query', function() { it('updates the doc', function() { const m = mquery({ name: name }); - const n = m.findOneAndRemove(m); + const n = m.findOneAndDelete(m); assert.deepEqual(n._conditions, { name: name }); }); }); - it('that is a function', async() => { + it('executes', async() => { await col.insertOne({ name: name }); const m = mquery({ name: name }).collection(col); - const res = await m.findOneAndRemove(); - assert.ok(res.value); - assert.equal(name, res.value.name); + const res = await m.findOneAndDelete(); + assert.ok(res); + assert.equal(name, res.name); }); }); describe('with 2 args', function() { it('conditions + options', function() { const m = mquery().collection(col); - m.findOneAndRemove({ name: name }, { returnDocument: 'after' }); + m.findOneAndDelete({ name: name }, { returnDocument: 'after' }); assert.deepEqual({ name: name }, m._conditions); assert.deepEqual({ returnDocument: 'after' }, m.options); }); it('query + options', function() { const n = mquery({ name: name }); const m = mquery().collection(col); - m.findOneAndRemove(n, { sort: { x: 1 } }); + m.findOneAndDelete(n, { sort: { x: 1 } }); assert.deepEqual({ name: name }, m._conditions); assert.deepEqual({ sort: { x: 1 } }, m.options); }); it('conditions + exec', async() => { await col.insertOne({ name: name }); const m = mquery().collection(col); - const res = await m.findOneAndRemove({ name: name }); - assert.equal(name, res.value.name); + const res = await m.findOneAndDelete({ name: name }); + assert.equal(name, res.name); }); it('query + exec', async() => { await col.insertOne({ name: name }); const n = mquery({ name: name }); const m = mquery().collection(col); - const res = await m.findOneAndRemove(n); - assert.equal(name, res.value.name); + const res = await m.findOneAndDelete(n); + assert.equal(name, res.name); }); - }); - describe('with 3 args', function() { it('conditions + options + exec', async() => { - name = 'findOneAndRemove + conds + options + cb'; + name = 'findOneAndDelete + conds + options + cb'; await col.insertMany([{ name: name }, { name: 'a' }]); const m = mquery().collection(col); - const res = await m.findOneAndRemove({ name: name }, { sort: { name: 1 } }); + const res = await m.findOneAndDelete({ name: name }, { sort: { name: 1 }, includeResultMetadata: true }); assert.ok(res.value); assert.equal(name, res.value.name); }); @@ -2210,7 +2296,7 @@ describe('mquery', function() { }); it('count', async() => { - const m = mquery().collection(col).count({ name: 'exec' }); + const m = mquery().collection(col).countDocuments({ name: 'exec' }); const count = await m.exec(); assert.equal(2, count); }); @@ -2231,14 +2317,14 @@ describe('mquery', function() { it('works', async() => { await mquery().collection(col).updateMany({ name: 'exec' }, { name: 'test' }). exec(); - const res = await mquery().collection(col).count({ name: 'test' }).exec(); + const res = await mquery().collection(col).countDocuments({ name: 'test' }).exec(); assert.equal(res, 2); }); it('works with write concern', async() => { await mquery().collection(col).updateMany({ name: 'exec' }, { name: 'test' }) .w(1).j(true).wtimeout(1000) .exec(); - const res = await mquery().collection(col).count({ name: 'test' }).exec(); + const res = await mquery().collection(col).countDocuments({ name: 'test' }).exec(); assert.equal(res, 2); }); }); @@ -2247,7 +2333,7 @@ describe('mquery', function() { it('works', async() => { await mquery().collection(col).updateOne({ name: 'exec' }, { name: 'test' }). exec(); - const res = await mquery().collection(col).count({ name: 'test' }).exec(); + const res = await mquery().collection(col).countDocuments({ name: 'test' }).exec(); assert.equal(res, 1); }); }); @@ -2291,20 +2377,20 @@ describe('mquery', function() { describe('findOneAndUpdate', function() { it('with exec', async() => { const m = mquery().collection(col); - m.findOneAndUpdate({ name: 'exec', age: 1 }, { $set: { name: 'findOneAndUpdate' } }, { returnDocument: 'after' }); + m.findOneAndUpdate({ name: 'exec', age: 1 }, { $set: { name: 'findOneAndUpdate' } }, { returnDocument: 'after', includeResultMetadata: true }); const res = await m.exec(); - assert.equal('findOneAndUpdate', res.value.name); + assert.equal(res.value.name, 'findOneAndUpdate'); }); }); - describe('findOneAndRemove', function() { + describe('findOneAndDelete', function() { it('with exec', async() => { const m = mquery().collection(col); - m.findOneAndRemove({ name: 'exec', age: 2 }); + m.findOneAndDelete({ name: 'exec', age: 2 }); const res = await m.exec(); - assert.equal('exec', res.value.name); - assert.equal(2, res.value.age); - const num = await mquery().collection(col).count({ name: 'exec' }); + assert.equal('exec', res.name); + assert.equal(2, res.age); + const num = await mquery().collection(col).countDocuments({ name: 'exec' }); assert.equal(1, num); }); }); @@ -2375,7 +2461,7 @@ describe('mquery', function() { }); it('creates a promise that is resolved on success', function(done) { - const promise = mquery().collection(col).count({ name: 'then' }).then(); + const promise = mquery().collection(col).countDocuments({ name: 'then' }).then(); promise.then(function(count) { assert.equal(2, count); done();