diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b74300e..2315522 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - node: [14, 16, 18] + node: [16, 18, 20] mongo: [4.2, 5.0] services: mongodb: diff --git a/lib/collection/collection.js b/lib/collection/collection.js index b473f89..3bea9a4 100644 --- a/lib/collection/collection.js +++ b/lib/collection/collection.js @@ -10,7 +10,8 @@ const methods = [ 'updateMany', 'updateOne', 'replaceOne', - 'count', + 'countDocuments', + 'estimatedDocumentCount', 'distinct', 'findOneAndDelete', 'findOneAndUpdate', diff --git a/lib/collection/node.js b/lib/collection/node.js index 6580f51..7421a9f 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); } /** diff --git a/lib/mquery.js b/lib/mquery.js index dc874ad..a38ff5e 100644 --- a/lib/mquery.js +++ b/lib/mquery.js @@ -1943,47 +1943,80 @@ Query.prototype._findOne = async function _findOne() { }; /** - * 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 `countDocuments` Query + * @returns the results + */ +Query.prototype._countDocuments = async function _countDocuments() { + const conds = this._conditions, + options = this._optionsForExec(); + + debug('countDocuments', this._collection.collectionName, conds, options); + + return this._collection.countDocuments(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._count = async function _count() { +Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount() { const conds = this._conditions, options = this._optionsForExec(); - debug('count', this._collection.collectionName, conds, options); + debug('estimatedDocumentCount', this._collection.collectionName, conds, options); - return this._collection.count(conds, options); + return this._collection.estimatedDocumentCount(conds, options); }; /** @@ -2329,7 +2362,7 @@ Query.prototype._findOneAndUpdate = async function() { }; /** - * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command. + * 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; diff --git a/lib/permissions.js b/lib/permissions.js index 97d2c73..1b6b9d6 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; @@ -54,12 +54,12 @@ denied.findOneAndUpdate.batchSize = denied.findOneAndUpdate.tailable = true; -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 +73,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..717f282 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "url": "git://github.com/aheckmann/mquery.git" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "dependencies": { "debug": "4.x" @@ -22,7 +22,7 @@ "eslint": "8.x", "eslint-plugin-mocha-no-only": "1.1.1", "mocha": "9.x", - "mongodb": "5.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..daec1c9 100644 --- a/test/index.js +++ b/test/index.js @@ -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() { @@ -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(); }); @@ -2016,8 +2016,8 @@ describe('mquery', function() { 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); + assert.ok(res); + assert.equal(res.name, name); }); }); describe('with 2 args', function() { @@ -2037,7 +2037,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' }); - assert.equal(10, res.value.age); + assert.equal(res.age, 10); }); }); describe('with 3 args', function() { @@ -2051,31 +2051,31 @@ describe('mquery', function() { it('conditions + update + exec', async() => { const m = mquery().collection(col); const res = await m.findOneAndUpdate({ name: name }, { works: true }, { returnDocument: 'after' }); - assert.ok(res.value); - assert.equal(name, res.value.name); - assert.ok(true === res.value.works); + assert.ok(res); + assert.equal(res.name, name); + assert.ok(res.works); }); }); describe('with 4 args', function() { it('conditions + update + options + exec', async() => { const m = mquery().collection(col); const res = await m.findOneAndUpdate({ name: name }, { works: false }, {}); - assert.ok(res.value); - assert.equal(name, res.value.name); - assert.ok(true === res.value.works); + assert.ok(res); + assert.equal(res.name, name); + assert.ok(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,61 +2083,61 @@ 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() => { 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(res.name, 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(res.name, 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(res.name, 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 } }); - assert.ok(res.value); - assert.equal(name, res.value.name); + const res = await m.findOneAndDelete({ name: name }, { sort: { name: 1 } }); + assert.ok(res); + assert.equal(res.name, name); }); }); }); @@ -2210,7 +2210,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 +2231,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 +2247,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); }); }); @@ -2293,19 +2293,19 @@ describe('mquery', function() { const m = mquery().collection(col); m.findOneAndUpdate({ name: 'exec', age: 1 }, { $set: { name: 'findOneAndUpdate' } }, { returnDocument: 'after' }); const res = await m.exec(); - assert.equal('findOneAndUpdate', res.value.name); + assert.equal(res.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(1, num); + assert.equal(res.name, 'exec'); + assert.equal(res.age, 2); + const num = await mquery().collection(col).countDocuments({ name: 'exec' }); + assert.equal(num, 1); }); }); }); @@ -2375,7 +2375,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();