From 6ab75535da09732f7c666c043d3a777fefdf91da Mon Sep 17 00:00:00 2001 From: Ankit Singh <155445436+singhankit001@users.noreply.github.com> Date: Sat, 29 Nov 2025 20:23:39 +0000 Subject: [PATCH 1/4] refactor: reuse sort logic between Query and Aggregate --- lib/aggregate.js | 99 ++++------ lib/helpers/query/castSort.js | 67 +++++++ lib/query.js | 246 +++++++++++-------------- test/aggregate.test.js | 333 ++++++++++++++++++---------------- 4 files changed, 382 insertions(+), 363 deletions(-) create mode 100644 lib/helpers/query/castSort.js diff --git a/lib/aggregate.js b/lib/aggregate.js index 560c9e228c8..5ff659d12a3 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -9,6 +9,7 @@ const MongooseError = require('./error/mongooseError'); const Query = require('./query'); const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption'); const clone = require('./helpers/clone'); +const castSort = require('./helpers/query/castSort'); const getConstructorName = require('./helpers/getConstructorName'); const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline'); const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators'); @@ -99,7 +100,7 @@ Aggregate.prototype.options; * @api private */ -Aggregate.prototype._optionsForExec = function() { +Aggregate.prototype._optionsForExec = function () { const options = this.options || {}; const asyncLocalStorage = this.model()?.db?.base.transactionAsyncLocalStorage?.getStore(); @@ -127,7 +128,7 @@ Aggregate.prototype._optionsForExec = function() { * @api public */ -Aggregate.prototype.model = function(model) { +Aggregate.prototype.model = function (model) { if (arguments.length === 0) { return this._model; } @@ -163,7 +164,7 @@ Aggregate.prototype.model = function(model) { * @api public */ -Aggregate.prototype.append = function() { +Aggregate.prototype.append = function () { const args = (arguments.length === 1 && Array.isArray(arguments[0])) ? arguments[0] : [...arguments]; @@ -200,7 +201,7 @@ Aggregate.prototype.append = function() { * @return {Aggregate} * @api public */ -Aggregate.prototype.addFields = function(arg) { +Aggregate.prototype.addFields = function (arg) { if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) { throw new Error('Invalid addFields() argument. Must be an object'); } @@ -239,15 +240,15 @@ Aggregate.prototype.addFields = function(arg) { * @api public */ -Aggregate.prototype.project = function(arg) { +Aggregate.prototype.project = function (arg) { const fields = {}; if (typeof arg === 'object' && !Array.isArray(arg)) { - Object.keys(arg).forEach(function(field) { + Object.keys(arg).forEach(function (field) { fields[field] = arg[field]; }); } else if (arguments.length === 1 && typeof arg === 'string') { - arg.split(/\s+/).forEach(function(field) { + arg.split(/\s+/).forEach(function (field) { if (!field) { return; } @@ -402,7 +403,7 @@ Aggregate.prototype.project = function(arg) { * @api public */ -Aggregate.prototype.near = function(arg) { +Aggregate.prototype.near = function (arg) { if (arg == null) { throw new MongooseError('Aggregate `near()` must be called with non-nullish argument'); } @@ -423,8 +424,8 @@ Aggregate.prototype.near = function(arg) { * define methods */ -'group match skip limit out densify fill'.split(' ').forEach(function($operator) { - Aggregate.prototype[$operator] = function(arg) { +'group match skip limit out densify fill'.split(' ').forEach(function ($operator) { + Aggregate.prototype[$operator] = function (arg) { const op = {}; op['$' + $operator] = arg; return this.append(op); @@ -449,7 +450,7 @@ Aggregate.prototype.near = function(arg) { * @api public */ -Aggregate.prototype.unwind = function() { +Aggregate.prototype.unwind = function () { const args = [...arguments]; const res = []; @@ -488,7 +489,7 @@ Aggregate.prototype.unwind = function() { * @api public */ -Aggregate.prototype.replaceRoot = function(newRoot) { +Aggregate.prototype.replaceRoot = function (newRoot) { let ret; if (typeof newRoot === 'string') { @@ -517,7 +518,7 @@ Aggregate.prototype.replaceRoot = function(newRoot) { * @api public */ -Aggregate.prototype.count = function(fieldName) { +Aggregate.prototype.count = function (fieldName) { return this.append({ $count: fieldName }); }; @@ -539,7 +540,7 @@ Aggregate.prototype.count = function(fieldName) { * @api public */ -Aggregate.prototype.sortByCount = function(arg) { +Aggregate.prototype.sortByCount = function (arg) { if (arg && typeof arg === 'object') { return this.append({ $sortByCount: arg }); } else if (typeof arg === 'string') { @@ -565,7 +566,7 @@ Aggregate.prototype.sortByCount = function(arg) { * @api public */ -Aggregate.prototype.lookup = function(options) { +Aggregate.prototype.lookup = function (options) { return this.append({ $lookup: options }); }; @@ -585,7 +586,7 @@ Aggregate.prototype.lookup = function(options) { * @api public */ -Aggregate.prototype.graphLookup = function(options) { +Aggregate.prototype.graphLookup = function (options) { const cloneOptions = {}; if (options) { if (!utils.isObject(options)) { @@ -618,7 +619,7 @@ Aggregate.prototype.graphLookup = function(options) { * @api public */ -Aggregate.prototype.sample = function(size) { +Aggregate.prototype.sample = function (size) { return this.append({ $sample: { size: size } }); }; @@ -641,35 +642,11 @@ Aggregate.prototype.sample = function(size) { * @api public */ -Aggregate.prototype.sort = function(arg) { - // TODO refactor to reuse the query builder logic - - const sort = {}; - - if (getConstructorName(arg) === 'Object') { - const desc = ['desc', 'descending', -1]; - Object.keys(arg).forEach(function(field) { - // If sorting by text score, skip coercing into 1/-1 - if (arg[field] instanceof Object && arg[field].$meta) { - sort[field] = arg[field]; - return; - } - sort[field] = desc.indexOf(arg[field]) === -1 ? 1 : -1; - }); - } else if (arguments.length === 1 && typeof arg === 'string') { - arg.split(/\s+/).forEach(function(field) { - if (!field) { - return; - } - const ascend = field[0] === '-' ? -1 : 1; - if (ascend === -1) { - field = field.substring(1); - } - sort[field] = ascend; - }); - } else { +Aggregate.prototype.sort = function (arg) { + if (arguments.length > 1) { throw new TypeError('Invalid sort() argument. Must be a string or object.'); } + const sort = castSort(arg); return this.append({ $sort: sort }); }; @@ -687,7 +664,7 @@ Aggregate.prototype.sort = function(arg) { * @api public */ -Aggregate.prototype.unionWith = function(options) { +Aggregate.prototype.unionWith = function (options) { return this.append({ $unionWith: options }); }; @@ -706,7 +683,7 @@ Aggregate.prototype.unionWith = function(options) { * @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference */ -Aggregate.prototype.read = function(pref, tags) { +Aggregate.prototype.read = function (pref, tags) { read.call(this, pref, tags); return this; }; @@ -724,7 +701,7 @@ Aggregate.prototype.read = function(pref, tags) { * @api public */ -Aggregate.prototype.readConcern = function(level) { +Aggregate.prototype.readConcern = function (level) { readConcern.call(this, level); return this; }; @@ -756,7 +733,7 @@ Aggregate.prototype.readConcern = function(level) { * @api public */ -Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) { +Aggregate.prototype.redact = function (expression, thenExpr, elseExpr) { if (arguments.length === 3) { if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) || (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) { @@ -836,7 +813,7 @@ Aggregate.prototype.explain = async function explain(verbosity) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.allowDiskUse = function(value) { +Aggregate.prototype.allowDiskUse = function (value) { this.options.allowDiskUse = value; return this; }; @@ -853,7 +830,7 @@ Aggregate.prototype.allowDiskUse = function(value) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.hint = function(value) { +Aggregate.prototype.hint = function (value) { this.options.hint = value; return this; }; @@ -871,7 +848,7 @@ Aggregate.prototype.hint = function(value) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.session = function(session) { +Aggregate.prototype.session = function (session) { if (session == null) { delete this.options.session; } else { @@ -898,7 +875,7 @@ Aggregate.prototype.session = function(session) { * @api public */ -Aggregate.prototype.option = function(value) { +Aggregate.prototype.option = function (value) { for (const key in value) { this.options[key] = value[key]; } @@ -925,7 +902,7 @@ Aggregate.prototype.option = function(value) { * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html */ -Aggregate.prototype.cursor = function(options) { +Aggregate.prototype.cursor = function (options) { this._optionsForExec(); this.options.cursor = options || {}; return new AggregationCursor(this); // return this; @@ -944,7 +921,7 @@ Aggregate.prototype.cursor = function(options) { * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/interfaces/CollationOptions.html */ -Aggregate.prototype.collation = function(collation) { +Aggregate.prototype.collation = function (collation) { this.options.collation = collation; return this; }; @@ -967,7 +944,7 @@ Aggregate.prototype.collation = function(collation) { * @api public */ -Aggregate.prototype.facet = function(options) { +Aggregate.prototype.facet = function (options) { return this.append({ $facet: options }); }; @@ -993,7 +970,7 @@ Aggregate.prototype.facet = function(options) { * @api public */ -Aggregate.prototype.search = function(options) { +Aggregate.prototype.search = function (options) { return this.append({ $search: options }); }; @@ -1008,7 +985,7 @@ Aggregate.prototype.search = function(options) { * @api public */ -Aggregate.prototype.pipeline = function() { +Aggregate.prototype.pipeline = function () { return this._pipeline; }; @@ -1093,7 +1070,7 @@ Aggregate.prototype.exec = async function exec() { * @param {Function} [reject] errorCallback * @return {Promise} */ -Aggregate.prototype.then = function(resolve, reject) { +Aggregate.prototype.then = function (resolve, reject) { return this.exec().then(resolve, reject); }; @@ -1108,7 +1085,7 @@ Aggregate.prototype.then = function(resolve, reject) { * @api public */ -Aggregate.prototype.catch = function(reject) { +Aggregate.prototype.catch = function (reject) { return this.exec().then(null, reject); }; @@ -1123,7 +1100,7 @@ Aggregate.prototype.catch = function(reject) { * @api public */ -Aggregate.prototype.finally = function(onFinally) { +Aggregate.prototype.finally = function (onFinally) { return this.exec().finally(onFinally); }; @@ -1145,7 +1122,7 @@ Aggregate.prototype.finally = function(onFinally) { * @api public */ -Aggregate.prototype[Symbol.asyncIterator] = function() { +Aggregate.prototype[Symbol.asyncIterator] = function () { return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator(); }; diff --git a/lib/helpers/query/castSort.js b/lib/helpers/query/castSort.js new file mode 100644 index 00000000000..65c1dc1ea4c --- /dev/null +++ b/lib/helpers/query/castSort.js @@ -0,0 +1,67 @@ +'use strict'; + +const specialProperties = require('../specialProperties'); + +module.exports = function castSort(arg) { + const sort = {}; + + if (typeof arg === 'string') { + const properties = arg.indexOf(' ') === -1 ? [arg] : arg.split(' '); + for (let property of properties) { + if (!property) { + continue; + } + const ascend = '-' == property[0] ? -1 : 1; + if (ascend === -1) { + property = property.slice(1); + } + if (specialProperties.has(property)) { + continue; + } + sort[property] = ascend; + } + } else if (Array.isArray(arg)) { + for (const pair of arg) { + if (!Array.isArray(pair)) { + throw new TypeError('Invalid sort() argument, must be array of arrays'); + } + const key = '' + pair[0]; + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(pair[1], key); + } + } else if (typeof arg === 'object' && arg != null && !(arg instanceof Map)) { + for (const key of Object.keys(arg)) { + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(arg[key], key); + } + } else if (arg instanceof Map) { + for (let key of arg.keys()) { + key = '' + key; + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(arg.get(key), key); + } + } else if (arg != null) { + throw new TypeError('Invalid sort() argument. Must be a string, object, array, or map.'); + } + + return sort; +}; + +function _handleSortValue(val, key) { + if (val === 1 || val === 'asc' || val === 'ascending') { + return 1; + } + if (val === -1 || val === 'desc' || val === 'descending') { + return -1; + } + if (val?.$meta != null) { + return { $meta: val.$meta }; + } + throw new TypeError('Invalid sort value: { ' + key + ': ' + val + ' }'); +} diff --git a/lib/query.js b/lib/query.js index 57bc291340f..945a6697280 100644 --- a/lib/query.js +++ b/lib/query.js @@ -18,6 +18,7 @@ const applyWriteConcern = require('./helpers/schema/applyWriteConcern'); const cast = require('./cast'); const castArrayFilters = require('./helpers/update/castArrayFilters'); const castNumber = require('./cast/number'); +const castSort = require('./helpers/query/castSort'); const castUpdate = require('./helpers/query/castUpdate'); const clone = require('./helpers/clone'); const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); @@ -274,7 +275,7 @@ Query.prototype.toConstructor = function toConstructor() { const model = this.model; const coll = this.mongooseCollection; - const CustomQuery = function(criteria, options) { + const CustomQuery = function (criteria, options) { if (!(this instanceof CustomQuery)) { return new CustomQuery(criteria, options); } @@ -331,7 +332,7 @@ Query.prototype.toConstructor = function toConstructor() { * @api public */ -Query.prototype.clone = function() { +Query.prototype.clone = function () { const model = this.model; const collection = this.mongooseCollection; @@ -457,7 +458,7 @@ Query.prototype.clone = function() { * @api public */ -Query.prototype.slice = function() { +Query.prototype.slice = function () { if (arguments.length === 0) { return this; } @@ -507,7 +508,7 @@ Query.prototype.slice = function() { const validOpsSet = new Set(queryMiddlewareFunctions); -Query.prototype._validateOp = function() { +Query.prototype._validateOp = function () { if (this.op != null && !validOpsSet.has(this.op)) { this.error(new Error('Query has invalid `op`: "' + this.op + '"')); } @@ -779,7 +780,7 @@ Query.prototype._validateOp = function() { * @api public */ -Query.prototype.mod = function() { +Query.prototype.mod = function () { let val; let path; @@ -1051,7 +1052,7 @@ Query.prototype.skip = function skip(v) { * @api public */ -Query.prototype.projection = function(arg) { +Query.prototype.projection = function (arg) { if (arguments.length === 0) { return this._fields; } @@ -1349,24 +1350,24 @@ Query.prototype.read = function read(mode, tags) { Query.prototype.toString = function toString() { if (this.op === 'count' || - this.op === 'countDocuments' || - this.op === 'find' || - this.op === 'findOne' || - this.op === 'deleteMany' || - this.op === 'deleteOne' || - this.op === 'findOneAndDelete' || - this.op === 'remove') { + this.op === 'countDocuments' || + this.op === 'find' || + this.op === 'findOne' || + this.op === 'deleteMany' || + this.op === 'deleteOne' || + this.op === 'findOneAndDelete' || + this.op === 'remove') { return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)})`; } if (this.op === 'distinct') { return `${this.model.modelName}.distinct('${this._distinct}', ${util.inspect(this._conditions)})`; } if (this.op === 'findOneAndReplace' || - this.op === 'findOneAndUpdate' || - this.op === 'replaceOne' || - this.op === 'update' || - this.op === 'updateMany' || - this.op === 'updateOne') { + this.op === 'findOneAndUpdate' || + this.op === 'replaceOne' || + this.op === 'update' || + this.op === 'updateMany' || + this.op === 'updateOne') { return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)}, ${util.inspect(this._update)})`; } @@ -1646,7 +1647,7 @@ Query.prototype.wtimeout = function wtimeout(ms) { * @api public */ -Query.prototype.getOptions = function() { +Query.prototype.getOptions = function () { return this.options; }; @@ -1706,7 +1707,7 @@ Query.prototype.getOptions = function() { * @api public */ -Query.prototype.setOptions = function(options, overwrite) { +Query.prototype.setOptions = function (options, overwrite) { // overwrite is only for internal use if (overwrite) { // ensure that _mongooseOptions & options are two different objects @@ -1866,7 +1867,7 @@ Query.prototype.explain = function explain(verbose) { * @api public */ -Query.prototype.allowDiskUse = function(v) { +Query.prototype.allowDiskUse = function (v) { if (arguments.length === 0) { this.options.allowDiskUse = true; } else if (v === false) { @@ -1896,7 +1897,7 @@ Query.prototype.allowDiskUse = function(v) { * @api public */ -Query.prototype.maxTimeMS = function(ms) { +Query.prototype.maxTimeMS = function (ms) { this.options.maxTimeMS = ms; return this; }; @@ -1914,7 +1915,7 @@ Query.prototype.maxTimeMS = function(ms) { * @api public */ -Query.prototype.getFilter = function() { +Query.prototype.getFilter = function () { return this._conditions; }; @@ -1934,7 +1935,7 @@ Query.prototype.getFilter = function() { * @api public */ -Query.prototype.getQuery = function() { +Query.prototype.getQuery = function () { return this._conditions; }; @@ -1953,7 +1954,7 @@ Query.prototype.getQuery = function() { * @api public */ -Query.prototype.setQuery = function(val) { +Query.prototype.setQuery = function (val) { this._conditions = val; }; @@ -1970,7 +1971,7 @@ Query.prototype.setQuery = function(val) { * @api public */ -Query.prototype.getUpdate = function() { +Query.prototype.getUpdate = function () { return this._update; }; @@ -1989,7 +1990,7 @@ Query.prototype.getUpdate = function() { * @api public */ -Query.prototype.setUpdate = function(val) { +Query.prototype.setUpdate = function (val) { this._update = val; }; @@ -2002,7 +2003,7 @@ Query.prototype.setUpdate = function(val) { * @memberOf Query */ -Query.prototype._fieldsForExec = function() { +Query.prototype._fieldsForExec = function () { if (this._fields == null) { return null; } @@ -2022,7 +2023,7 @@ Query.prototype._fieldsForExec = function() { * @memberOf Query */ -Query.prototype._updateForExec = function() { +Query.prototype._updateForExec = function () { const update = clone(this._update, { transform: false, depopulate: true @@ -2087,7 +2088,7 @@ Query.prototype._updateForExec = function() { * @api private */ -Query.prototype._optionsForExec = function(model) { +Query.prototype._optionsForExec = function (model) { const options = clone(this.options); delete options.populate; model = model || this.model; @@ -2105,9 +2106,9 @@ Query.prototype._optionsForExec = function(model) { } const readPreference = model && - model.schema && - model.schema.options && - model.schema.options.read; + model.schema && + model.schema.options && + model.schema.options.read; if (!('readPreference' in options) && readPreference) { options.readPreference = readPreference; } @@ -2197,7 +2198,7 @@ Query.prototype._optionsForExec = function(model) { * @api public */ -Query.prototype.lean = function(v) { +Query.prototype.lean = function (v) { this._mongooseOptions.lean = arguments.length ? v : true; return this; }; @@ -2219,7 +2220,7 @@ Query.prototype.lean = function(v) { * @api public */ -Query.prototype.set = function(path, val) { +Query.prototype.set = function (path, val) { if (typeof path === 'object') { const keys = Object.keys(path); for (const key of keys) { @@ -2347,7 +2348,7 @@ Query.prototype._unsetCastError = function _unsetCastError() { * @api public */ -Query.prototype.mongooseOptions = function(v) { +Query.prototype.mongooseOptions = function (v) { if (arguments.length > 0) { this._mongooseOptions = v; } @@ -2362,7 +2363,7 @@ Query.prototype.mongooseOptions = function(v) { * @instance */ -Query.prototype._castConditions = function() { +Query.prototype._castConditions = function () { let sanitizeFilterOpt = undefined; if (this.model?.db.options?.sanitizeFilter != null) { sanitizeFilterOpt = this.model.db.options.sanitizeFilter; @@ -2479,9 +2480,9 @@ Query.prototype._find = async function _find() { * @api public */ -Query.prototype.find = function(conditions) { +Query.prototype.find = function (conditions) { if (typeof conditions === 'function' || - typeof arguments[1] === 'function') { + typeof arguments[1] === 'function') { throw new MongooseError('Query.prototype.find() no longer accepts a callback'); } @@ -2507,7 +2508,7 @@ Query.prototype.find = function(conditions) { * @return {Query} this */ -Query.prototype.merge = function(source) { +Query.prototype.merge = function (source) { if (!source) { if (source === null) { this._conditions = null; @@ -2600,7 +2601,7 @@ Query.prototype.merge = function(source) { * @api public */ -Query.prototype.collation = function(value) { +Query.prototype.collation = function (value) { if (this.options == null) { this.options = {}; } @@ -2614,7 +2615,7 @@ Query.prototype.collation = function(value) { * @api private */ -Query.prototype._completeOne = function(doc, res, projection, callback) { +Query.prototype._completeOne = function (doc, res, projection, callback) { if (!doc && !this.options.includeResultMetadata) { return callback(null, null); } @@ -2758,11 +2759,11 @@ Query.prototype._findOne = async function _findOne() { * @api public */ -Query.prototype.findOne = function(conditions, projection, options) { +Query.prototype.findOne = function (conditions, projection, options) { if (typeof conditions === 'function' || - typeof projection === 'function' || - typeof options === 'function' || - typeof arguments[3] === 'function') { + typeof projection === 'function' || + typeof options === 'function' || + typeof arguments[3] === 'function') { throw new MongooseError('Query.prototype.findOne() no longer accepts a callback'); } @@ -2886,9 +2887,9 @@ Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount * @api public */ -Query.prototype.estimatedDocumentCount = function(options) { +Query.prototype.estimatedDocumentCount = function (options) { if (typeof options === 'function' || - typeof arguments[1] === 'function') { + typeof arguments[1] === 'function') { throw new MongooseError('Query.prototype.estimatedDocumentCount() no longer accepts a callback'); } @@ -2939,10 +2940,10 @@ Query.prototype.estimatedDocumentCount = function(options) { * @api public */ -Query.prototype.countDocuments = function(conditions, options) { +Query.prototype.countDocuments = function (conditions, options) { if (typeof conditions === 'function' || - typeof options === 'function' || - typeof arguments[2] === 'function') { + typeof options === 'function' || + typeof arguments[2] === 'function') { throw new MongooseError('Query.prototype.countDocuments() no longer accepts a callback'); } @@ -3003,11 +3004,11 @@ Query.prototype.__distinct = async function __distinct() { * @api public */ -Query.prototype.distinct = function(field, conditions, options) { +Query.prototype.distinct = function (field, conditions, options) { if (typeof field === 'function' || - typeof conditions === 'function' || - typeof options === 'function' || - typeof arguments[3] === 'function') { + typeof conditions === 'function' || + typeof options === 'function' || + typeof arguments[3] === 'function') { throw new MongooseError('Query.prototype.distinct() no longer accepts a callback'); } @@ -3064,7 +3065,7 @@ Query.prototype.distinct = function(field, conditions, options) { * @api public */ -Query.prototype.sort = function(arg, options) { +Query.prototype.sort = function (arg, options) { if (arguments.length > 2) { throw new Error('sort() takes at most 2 arguments'); } @@ -3078,48 +3079,9 @@ Query.prototype.sort = function(arg, options) { if (options && options.override) { this.options.sort = {}; } - const sort = this.options.sort; - if (typeof arg === 'string') { - const properties = arg.indexOf(' ') === -1 ? [arg] : arg.split(' '); - for (let property of properties) { - const ascend = '-' == property[0] ? -1 : 1; - if (ascend === -1) { - property = property.slice(1); - } - if (specialProperties.has(property)) { - continue; - } - sort[property] = ascend; - } - } else if (Array.isArray(arg)) { - for (const pair of arg) { - if (!Array.isArray(pair)) { - throw new TypeError('Invalid sort() argument, must be array of arrays'); - } - const key = '' + pair[0]; - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(pair[1], key); - } - } else if (typeof arg === 'object' && arg != null && !(arg instanceof Map)) { - for (const key of Object.keys(arg)) { - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(arg[key], key); - } - } else if (arg instanceof Map) { - for (let key of arg.keys()) { - key = '' + key; - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(arg.get(key), key); - } - } else if (arg != null) { - throw new TypeError('Invalid sort() argument. Must be a string, object, array, or map.'); - } + + const sort = castSort(arg); + Object.assign(this.options.sort, sort); return this; }; @@ -3250,7 +3212,7 @@ Query.prototype._deleteOne = async function _deleteOne() { * @api public */ -Query.prototype.deleteMany = function(filter, options) { +Query.prototype.deleteMany = function (filter, options) { if (typeof filter === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Query.prototype.deleteMany() no longer accepts a callback'); } @@ -3411,11 +3373,11 @@ function prepareDiscriminatorCriteria(query) { * @api public */ -Query.prototype.findOneAndUpdate = function(filter, update, options) { +Query.prototype.findOneAndUpdate = function (filter, update, options) { if (typeof filter === 'function' || - typeof update === 'function' || - typeof options === 'function' || - typeof arguments[3] === 'function') { + typeof update === 'function' || + typeof options === 'function' || + typeof arguments[3] === 'function') { throw new MongooseError('Query.prototype.findOneAndUpdate() no longer accepts a callback'); } @@ -3590,10 +3552,10 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { * @api public */ -Query.prototype.findOneAndDelete = function(filter, options) { +Query.prototype.findOneAndDelete = function (filter, options) { if (typeof filter === 'function' || - typeof options === 'function' || - typeof arguments[2] === 'function') { + typeof options === 'function' || + typeof arguments[2] === 'function') { throw new MongooseError('Query.prototype.findOneAndDelete() no longer accepts a callback'); } @@ -3691,11 +3653,11 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { * @api public */ -Query.prototype.findOneAndReplace = function(filter, replacement, options) { +Query.prototype.findOneAndReplace = function (filter, replacement, options) { if (typeof filter === 'function' || - typeof replacement === 'function' || - typeof options === 'function' || - typeof arguments[4] === 'function') { + typeof replacement === 'function' || + typeof options === 'function' || + typeof arguments[4] === 'function') { throw new MongooseError('Query.prototype.findOneAndReplace() no longer accepts a callback'); } @@ -3821,7 +3783,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() { * @api public */ -Query.prototype.findById = function(id, projection, options) { +Query.prototype.findById = function (id, projection, options) { return this.findOne({ _id: id }, projection, options); }; @@ -3862,7 +3824,7 @@ Query.prototype.findById = function(id, projection, options) { * @api public */ -Query.prototype.findByIdAndUpdate = function(id, update, options) { +Query.prototype.findByIdAndUpdate = function (id, update, options) { return this.findOneAndUpdate({ _id: id }, update, options); }; @@ -3887,7 +3849,7 @@ Query.prototype.findByIdAndUpdate = function(id, update, options) { * @api public */ -Query.prototype.findByIdAndDelete = function(id, options) { +Query.prototype.findByIdAndDelete = function (id, options) { return this.findOneAndDelete({ _id: id }, options); }; @@ -4006,7 +3968,7 @@ function _completeManyLean(schema, docs, path, opts) { * @api private */ -Query.prototype._mergeUpdate = function(update) { +Query.prototype._mergeUpdate = function (update) { const updatePipeline = this._mongooseOptions.updatePipeline; if (!updatePipeline && Array.isArray(update)) { throw new MongooseError('Cannot pass an array to query updates unless the `updatePipeline` option is set.'); @@ -4204,7 +4166,7 @@ Query.prototype._replaceOne = async function _replaceOne() { * @api public */ -Query.prototype.updateMany = function(conditions, doc, options, callback) { +Query.prototype.updateMany = function (conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4279,7 +4241,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) { * @api public */ -Query.prototype.updateOne = function(conditions, doc, options, callback) { +Query.prototype.updateOne = function (conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4346,7 +4308,7 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) { * @api public */ -Query.prototype.replaceOne = function(conditions, doc, options, callback) { +Query.prototype.replaceOne = function (conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4398,8 +4360,8 @@ function _update(query, op, filter, doc, options, callback) { } if (!(filter instanceof Query) && - filter != null && - filter.toString() !== '[object Object]') { + filter != null && + filter.toString() !== '[object Object]') { query.error(new ObjectParameterError(filter, 'filter', op)); } else { query.merge(filter); @@ -4450,7 +4412,7 @@ function _update(query, op, filter, doc, options, callback) { * @return {Query} this */ -Query.prototype.transform = function(fn) { +Query.prototype.transform = function (fn) { this._transforms.push(fn); return this; }; @@ -4485,7 +4447,7 @@ Query.prototype.transform = function(fn) { * @return {Query} this */ -Query.prototype.orFail = function(err) { +Query.prototype.orFail = function (err) { this.transform(res => { switch (this.op) { case 'find': @@ -4556,7 +4518,7 @@ function _orFailError(err, query) { * @api public */ -Query.prototype.isPathSelectedInclusive = function(path) { +Query.prototype.isPathSelectedInclusive = function (path) { return isPathSelectedInclusive(this._fields, path); }; @@ -4689,7 +4651,7 @@ function _executePreHooks(query, op) { * @api public */ -Query.prototype.then = function(resolve, reject) { +Query.prototype.then = function (resolve, reject) { return this.exec().then(resolve, reject); }; @@ -4705,7 +4667,7 @@ Query.prototype.then = function(resolve, reject) { * @api public */ -Query.prototype.catch = function(reject) { +Query.prototype.catch = function (reject) { return this.exec().then(null, reject); }; @@ -4720,7 +4682,7 @@ Query.prototype.catch = function(reject) { * @api public */ -Query.prototype.finally = function(onFinally) { +Query.prototype.finally = function (onFinally) { return this.exec().finally(onFinally); }; @@ -4764,7 +4726,7 @@ Query.prototype[Symbol.toStringTag] = function toString() { * @api public */ -Query.prototype.pre = function(fn) { +Query.prototype.pre = function (fn) { this._hooks.pre('exec', fn); return this; }; @@ -4790,7 +4752,7 @@ Query.prototype.pre = function(fn) { * @api public */ -Query.prototype.post = function(fn) { +Query.prototype.post = function (fn) { this._hooks.post('exec', fn); return this; }; @@ -4812,8 +4774,8 @@ Query.prototype._castUpdate = function _castUpdate(obj) { const discriminatorKey = schema.options.discriminatorKey; const baseSchema = schema._baseSchema ? schema._baseSchema : schema; if (this._mongooseOptions.overwriteDiscriminatorKey && - obj[discriminatorKey] != null && - baseSchema.discriminators) { + obj[discriminatorKey] != null && + baseSchema.discriminators) { const _schema = Object.values(baseSchema.discriminators).find( discriminator => discriminator.discriminatorMapping.value === obj[discriminatorKey] ); @@ -4888,7 +4850,7 @@ Query.prototype._castUpdate = function _castUpdate(obj) { * @api public */ -Query.prototype.populate = function() { +Query.prototype.populate = function () { const args = Array.from(arguments); // Bail when given no truthy arguments if (!args.some(Boolean)) { @@ -4990,12 +4952,12 @@ function _getPopulatedPaths(list, arr, prefix) { * @api public */ -Query.prototype.cast = function(model, obj) { +Query.prototype.cast = function (model, obj) { obj || (obj = this._conditions); model = model || this.model; const discriminatorKey = model.schema.options.discriminatorKey; if (obj != null && - obj.hasOwnProperty(discriminatorKey)) { + obj.hasOwnProperty(discriminatorKey)) { model = getDiscriminatorByValue(model.discriminators, obj[discriminatorKey]) || model; } @@ -5036,10 +4998,10 @@ Query.prototype.cast = function(model, obj) { Query.prototype._castFields = function _castFields(fields) { let selected, - elemMatchKeys, - keys, - key, - out; + elemMatchKeys, + keys, + key, + out; if (fields) { keys = Object.keys(fields); @@ -5194,7 +5156,7 @@ Query.prototype.cursor = function cursor(opts) { * @api public */ -Query.prototype.tailable = function(val, opts) { +Query.prototype.tailable = function (val, opts) { // we need to support the tailable({ awaitData : true }) as well as the // tailable(true, {awaitData :true}) syntax that mquery does not support if (val != null && typeof val.constructor === 'function' && val.constructor.name === 'Object') { @@ -5335,7 +5297,7 @@ Query.prototype.tailable = function(val, opts) { * @api private */ -Query.prototype.near = function() { +Query.prototype.near = function () { const params = []; const sphere = this._mongooseOptions.nearSphere; @@ -5372,7 +5334,7 @@ Query.prototype.near = function() { } } else if (arguments.length === 3) { if (typeof arguments[0] === 'string' && typeof arguments[1] === 'number' - && typeof arguments[2] === 'number') { + && typeof arguments[2] === 'number') { params.push(arguments[0]); params.push({ center: [arguments[1], arguments[2]], spherical: sphere }); } else { @@ -5405,7 +5367,7 @@ Query.prototype.near = function() { * @see $maxDistance https://www.mongodb.com/docs/manual/reference/operator/maxDistance/ */ -Query.prototype.nearSphere = function() { +Query.prototype.nearSphere = function () { this._mongooseOptions.nearSphere = true; this.near.apply(this, arguments); return this; @@ -5488,7 +5450,7 @@ Query.prototype[Symbol.asyncIterator] = function queryAsyncIterator() { * @api private */ -Query.prototype.box = function(ll, ur) { +Query.prototype.box = function (ll, ur) { if (!Array.isArray(ll) && utils.isObject(ll)) { ur = ll.ur; ll = ll.ll; @@ -5558,7 +5520,7 @@ Query.prototype.center = Query.base.circle; * @api public */ -Query.prototype.centerSphere = function() { +Query.prototype.centerSphere = function () { if (arguments[0] != null && typeof arguments[0].constructor === 'function' && arguments[0].constructor.name === 'Object') { arguments[0].spherical = true; } diff --git a/test/aggregate.test.js b/test/aggregate.test.js index 91d427567b0..9636670fdd3 100644 --- a/test/aggregate.test.js +++ b/test/aggregate.test.js @@ -72,7 +72,7 @@ async function onlyTestAtOrAbove(semver, ctx) { * Test. */ -describe('aggregate: ', function() { +describe('aggregate: ', function () { let db; before(function startConnection() { @@ -87,8 +87,8 @@ describe('aggregate: ', function() { afterEach(() => require('./util').clearTestData(db)); afterEach(() => require('./util').stopRemainingOps(db)); - describe('append', function() { - it('(pipeline)', function() { + describe('append', function () { + it('(pipeline)', function () { const aggregate = new Aggregate(); assert.equal(aggregate.append({ $a: 1 }, { $b: 2 }, { $c: 3 }), aggregate); @@ -98,7 +98,7 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $a: 1 }, { $b: 2 }, { $c: 3 }, { $d: 4 }, { $c: 5 }]); }); - it('supports array as single argument', function() { + it('supports array as single argument', function () { const aggregate = new Aggregate(); assert.equal(aggregate.append([{ $a: 1 }, { $b: 2 }, { $c: 3 }]), aggregate); @@ -108,46 +108,46 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $a: 1 }, { $b: 2 }, { $c: 3 }, { $d: 4 }, { $c: 5 }]); }); - it('throws if non-operator parameter is passed', function() { + it('throws if non-operator parameter is passed', function () { const aggregate = new Aggregate(); const regexp = /Arguments must be aggregate pipeline operators/; - assert.throws(function() { + assert.throws(function () { aggregate.append({ $a: 1 }, 'string'); }, regexp); - assert.throws(function() { + assert.throws(function () { aggregate.append({ $a: 1 }, ['array']); }, regexp); - assert.throws(function() { + assert.throws(function () { aggregate.append({ $a: 1 }, { a: 1 }); }, regexp); - assert.throws(function() { + assert.throws(function () { aggregate.append([{ $a: 1 }, { a: 1 }]); }, regexp); }); - it('does not throw when 0 args passed', function() { + it('does not throw when 0 args passed', function () { const aggregate = new Aggregate(); - assert.doesNotThrow(function() { + assert.doesNotThrow(function () { aggregate.append(); }); }); - it('does not throw when empty array is passed as single argument', function() { + it('does not throw when empty array is passed as single argument', function () { const aggregate = new Aggregate(); - assert.doesNotThrow(function() { + assert.doesNotThrow(function () { aggregate.append([]); }); }); }); - describe('project', function() { - it('(object)', function() { + describe('project', function () { + it('(object)', function () { const aggregate = new Aggregate(); assert.equal(aggregate.project({ a: 1, b: 1, c: 0 }), aggregate); @@ -157,7 +157,7 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $project: { a: 1, b: 1, c: 0 } }, { $project: { b: 1 } }]); }); - it('(string)', function() { + it('(string)', function () { const aggregate = new Aggregate(); aggregate.project(' a b -c '); @@ -167,23 +167,23 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $project: { a: 1, b: 1, c: 0 } }, { $project: { b: 1 } }]); }); - it('("a","b","c")', function() { - assert.throws(function() { + it('("a","b","c")', function () { + assert.throws(function () { const aggregate = new Aggregate(); aggregate.project('a', 'b', 'c'); }, /Invalid project/); }); - it('["a","b","c"]', function() { - assert.throws(function() { + it('["a","b","c"]', function () { + assert.throws(function () { const aggregate = new Aggregate(); aggregate.project(['a', 'b', 'c']); }, /Invalid project/); }); }); - describe('group', function() { - it('works', function() { + describe('group', function () { + it('works', function () { const aggregate = new Aggregate(); assert.equal(aggregate.group({ a: 1, b: 2 }), aggregate); @@ -194,8 +194,8 @@ describe('aggregate: ', function() { }); }); - describe('skip', function() { - it('works', function() { + describe('skip', function () { + it('works', function () { const aggregate = new Aggregate(); assert.equal(aggregate.skip(42), aggregate); @@ -206,8 +206,8 @@ describe('aggregate: ', function() { }); }); - describe('limit', function() { - it('works', function() { + describe('limit', function () { + it('works', function () { const aggregate = new Aggregate(); assert.equal(aggregate.limit(42), aggregate); @@ -218,8 +218,8 @@ describe('aggregate: ', function() { }); }); - describe('unwind', function() { - it('("field")', function() { + describe('unwind', function () { + it('("field")', function () { const aggregate = new Aggregate(); assert.equal(aggregate.unwind('field'), aggregate); @@ -235,8 +235,8 @@ describe('aggregate: ', function() { }); }); - describe('match', function() { - it('works', function() { + describe('match', function () { + it('works', function () { const aggregate = new Aggregate(); assert.equal(aggregate.match({ a: 1 }), aggregate); @@ -247,8 +247,8 @@ describe('aggregate: ', function() { }); }); - describe('sort', function() { - it('(object)', function() { + describe('sort', function () { + it('(object)', function () { const aggregate = new Aggregate(); assert.equal(aggregate.sort({ a: 1, b: 'asc', c: 'descending' }), aggregate); @@ -258,7 +258,7 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $sort: { a: 1, b: 1, c: -1 } }, { $sort: { b: -1 } }]); }); - it('(string)', function() { + it('(string)', function () { const aggregate = new Aggregate(); aggregate.sort(' a b -c '); @@ -268,23 +268,36 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $sort: { a: 1, b: 1, c: -1 } }, { $sort: { b: 1 } }]); }); - it('("a","b","c")', function() { - assert.throws(function() { + it('("a","b","c")', function () { + assert.throws(function () { const aggregate = new Aggregate(); aggregate.sort('a', 'b', 'c'); }, /Invalid sort/); }); - it('["a","b","c"]', function() { - assert.throws(function() { + it('["a","b","c"]', function () { + assert.throws(function () { const aggregate = new Aggregate(); aggregate.sort(['a', 'b', 'c']); }, /Invalid sort/); }); + + it('should support Maps for sort', function () { + const aggregate = new Aggregate(); + const map = new Map([['name', 'asc'], ['age', -1]]); + aggregate.sort(map); + assert.deepEqual(aggregate._pipeline, [{ $sort: { name: 1, age: -1 } }]); + }); + + it('should support array of arrays for sort', function () { + const aggregate = new Aggregate(); + aggregate.sort([['name', 'asc'], ['age', -1]]); + assert.deepEqual(aggregate._pipeline, [{ $sort: { name: 1, age: -1 } }]); + }); }); - describe('near', function() { - it('works', function() { + describe('near', function () { + it('works', function () { const aggregate = new Aggregate(); assert.equal(aggregate.near({ near: { type: 'Point', coordinates: [1, 2] } }), aggregate); @@ -297,7 +310,7 @@ describe('aggregate: ', function() { ]); }); - it('works with discriminators (gh-3304)', function() { + it('works with discriminators (gh-3304)', function () { let aggregate = new Aggregate(); const stub = { schema: { @@ -327,8 +340,8 @@ describe('aggregate: ', function() { }); }); - describe('lookup', function() { - it('works', function() { + describe('lookup', function () { + it('works', function () { const aggregate = new Aggregate(); const obj = { from: 'users', @@ -344,8 +357,8 @@ describe('aggregate: ', function() { }); }); - describe('unionWith', function() { - it('works', function() { + describe('unionWith', function () { + it('works', function () { const aggregate = new Aggregate(); const obj = { coll: 'users', @@ -363,8 +376,8 @@ describe('aggregate: ', function() { }); }); - describe('sample', function() { - it('works', function() { + describe('sample', function () { + it('works', function () { const aggregate = new Aggregate(); aggregate.sample(3); @@ -374,8 +387,8 @@ describe('aggregate: ', function() { }); }); - describe('densify', function() { - it('works', function() { + describe('densify', function () { + it('works', function () { const aggregate = new Aggregate(); const obj = { field: 'timestamp', @@ -393,16 +406,16 @@ describe('aggregate: ', function() { }); }); - describe('fill', function() { - it('works', function() { + describe('fill', function () { + it('works', function () { const aggregate = new Aggregate(); const obj = { output: - { - bootsSold: { value: 0 }, - sandalsSold: { value: 0 }, - sneakersSold: { value: 0 } - } + { + bootsSold: { value: 0 }, + sandalsSold: { value: 0 }, + sneakersSold: { value: 0 } + } }; aggregate.fill(obj); @@ -412,8 +425,8 @@ describe('aggregate: ', function() { }); }); - describe('model()', function() { - it('works', function() { + describe('model()', function () { + it('works', function () { const aggregate = new Aggregate(); const model = { foo: 42 }; @@ -426,9 +439,9 @@ describe('aggregate: ', function() { }); }); - describe('redact', function() { + describe('redact', function () { const pipelineResult = [{ $redact: { $cond: { if: { $eq: ['$level', 5] }, then: '$$PRUNE', else: '$$DESCEND' } } }]; - it('works', function() { + it('works', function () { const aggregate = new Aggregate(); aggregate.redact({ $cond: { @@ -439,20 +452,20 @@ describe('aggregate: ', function() { }); assert.deepEqual(aggregate._pipeline, pipelineResult); }); - it('works with (condition, string, string)', function() { + it('works with (condition, string, string)', function () { const aggregate = new Aggregate(); aggregate.redact({ $eq: ['$level', 5] }, '$$PRUNE', '$$DESCEND'); assert.deepEqual(aggregate._pipeline, pipelineResult); }); }); - describe('Mongo 3.4 operators', function() { - before(async function() { + describe('Mongo 3.4 operators', function () { + before(async function () { await onlyTestAtOrAbove('3.4', this); }); - describe('graphLookup', function() { - it('works', function() { + describe('graphLookup', function () { + it('works', function () { const aggregate = new Aggregate(); aggregate.graphLookup({ startWith: '$test', @@ -470,7 +483,7 @@ describe('aggregate: ', function() { }); }); - it('automatically prepends $ to the startWith field', function() { + it('automatically prepends $ to the startWith field', function () { const aggregate = new Aggregate(); aggregate.graphLookup({ startWith: 'test' @@ -481,29 +494,29 @@ describe('aggregate: ', function() { }); }); - it('Throws if no options are passed to graphLookup', function() { + it('Throws if no options are passed to graphLookup', function () { const aggregate = new Aggregate(); - assert.throws(function() { + assert.throws(function () { aggregate.graphLookup('invalid options'); }, - TypeError); + TypeError); }); }); - describe('addFields', function() { - it('should throw if passed a non object', function() { + describe('addFields', function () { + it('should throw if passed a non object', function () { const aggregate = new Aggregate(); - assert.throws(() => {aggregate.addFields('invalid');}, /Invalid addFields\(\) argument\. Must be an object/); + assert.throws(() => { aggregate.addFields('invalid'); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('should throw if passed null', function() { + it('should throw if passed null', function () { const aggregate = new Aggregate(); - assert.throws(() => {aggregate.addFields(null);}, /Invalid addFields\(\) argument\. Must be an object/); + assert.throws(() => { aggregate.addFields(null); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('should throw if passed an Array', function() { + it('should throw if passed an Array', function () { const aggregate = new Aggregate(); - assert.throws(() => {aggregate.addFields([]);}, /Invalid addFields\(\) argument\. Must be an object/); + assert.throws(() => { aggregate.addFields([]); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('(object)', function() { + it('(object)', function () { const aggregate = new Aggregate(); assert.equal(aggregate.addFields({ a: 1, b: 1, c: 0 }), aggregate); @@ -514,8 +527,8 @@ describe('aggregate: ', function() { }); }); - describe('facet', function() { - it('works', function() { + describe('facet', function () { + it('works', function () { const aggregate = new Aggregate(); aggregate.facet({ @@ -560,8 +573,8 @@ describe('aggregate: ', function() { }); }); - describe('replaceRoot', function() { - it('works with a string', function() { + describe('replaceRoot', function () { + it('works with a string', function () { const aggregate = new Aggregate(); aggregate.replaceRoot('myNewRoot'); @@ -569,7 +582,7 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $replaceRoot: { newRoot: '$myNewRoot' } }]); }); - it('works with an object (gh-6474)', function() { + it('works with an object (gh-6474)', function () { const aggregate = new Aggregate(); aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } }); @@ -579,8 +592,8 @@ describe('aggregate: ', function() { }); }); - describe('count', function() { - it('works', function() { + describe('count', function () { + it('works', function () { const aggregate = new Aggregate(); aggregate.count('countResult'); @@ -589,8 +602,8 @@ describe('aggregate: ', function() { }); }); - describe('sortByCount', function() { - it('works with a string argument', function() { + describe('sortByCount', function () { + it('works with a string argument', function () { const aggregate = new Aggregate(); aggregate.sortByCount('countedField'); @@ -598,7 +611,7 @@ describe('aggregate: ', function() { assert.deepEqual(aggregate._pipeline, [{ $sortByCount: '$countedField' }]); }); - it('works with an object argument', function() { + it('works with an object argument', function () { const aggregate = new Aggregate(); aggregate.sortByCount({ lname: '$employee.last' }); @@ -607,33 +620,33 @@ describe('aggregate: ', function() { [{ $sortByCount: { lname: '$employee.last' } }]); }); - it('throws if the argument is neither a string or object', function() { + it('throws if the argument is neither a string or object', function () { const aggregate = new Aggregate(); - assert.throws(function() { + assert.throws(function () { aggregate.sortByCount(1); }, TypeError); }); }); }); - describe('exec', function() { - beforeEach(async function() { + describe('exec', function () { + beforeEach(async function () { this.timeout(4000); // double the default of 2 seconds await setupData(db); }); - it('project', async function() { + it('project', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.project({ sal: 1, sal_k: { $divide: ['$sal', 1000] } }).exec(); - docs.forEach(function(doc) { + docs.forEach(function (doc) { assert.equal(doc.sal / 1000, doc.sal_k); }); }); - it('group', async function() { + it('group', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -647,7 +660,7 @@ describe('aggregate: ', function() { assert.notEqual(depts.indexOf('r&d'), -1); }); - it('skip', async function() { + it('skip', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -657,7 +670,7 @@ describe('aggregate: ', function() { assert.equal(docs.length, 3); }); - it('limit', async function() { + it('limit', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -667,7 +680,7 @@ describe('aggregate: ', function() { assert.equal(docs.length, 3); }); - it('unwind', async function() { + it('unwind', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -677,7 +690,7 @@ describe('aggregate: ', function() { assert.equal(docs.length, 5); }); - it('unwind with obj', function() { + it('unwind with obj', function () { const aggregate = new Aggregate(); const agg = aggregate. @@ -688,7 +701,7 @@ describe('aggregate: ', function() { true); }); - it('unwind throws with bad arg', function() { + it('unwind throws with bad arg', function () { const aggregate = new Aggregate(); let threw = false; @@ -702,7 +715,7 @@ describe('aggregate: ', function() { assert.ok(threw); }); - it('match', async function() { + it('match', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.match({ sal: { $gt: 15000 } }); @@ -710,7 +723,7 @@ describe('aggregate: ', function() { assert.equal(docs.length, 1); }); - it('sort', async function() { + it('sort', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.sort('sal'); @@ -718,7 +731,7 @@ describe('aggregate: ', function() { assert.equal(docs[0].sal, 14000); }); - it('graphLookup', async function() { + it('graphLookup', async function () { const _this = this; const version = await start.mongodVersion(); @@ -751,7 +764,7 @@ describe('aggregate: ', function() { assert.equal(names[2], 'Carol'); }); - it('facet', async function() { + it('facet', async function () { const _this = this; const version = await start.mongodVersion(); @@ -789,7 +802,7 @@ describe('aggregate: ', function() { ]); }); - it('complex pipeline', async function() { + it('complex pipeline', async function () { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -805,7 +818,7 @@ describe('aggregate: ', function() { assert.equal(docs[0].emp, 'Bob'); }); - it('pipeline() (gh-5825)', function() { + it('pipeline() (gh-5825)', function () { const aggregate = new Aggregate(); const pipeline = aggregate. @@ -815,7 +828,7 @@ describe('aggregate: ', function() { assert.deepEqual(pipeline, [{ $match: { sal: { $lt: 16000 } } }]); }); - it('explain()', async function() { + it('explain()', async function () { const aggregate = new Aggregate([], db.model('Employee')); const output = await aggregate. @@ -827,8 +840,8 @@ describe('aggregate: ', function() { assert.ok(output.stages || output.queryPlanner); }); - describe('error when empty pipeline', function() { - it('without a callback', function() { + describe('error when empty pipeline', function () { + it('without a callback', function () { const agg = new Aggregate([], db.model('Employee')); const promise = agg.exec(); @@ -841,8 +854,8 @@ describe('aggregate: ', function() { }); }); - describe('error when not bound to a model', function() { - it('with callback', async function() { + describe('error when not bound to a model', function () { + it('with callback', async function () { const aggregate = new Aggregate(); aggregate.skip(0); @@ -855,7 +868,7 @@ describe('aggregate: ', function() { }); }); - it('handles aggregation options', async function() { + it('handles aggregation options', async function () { const version = await start.mongodVersion(); const m = db.model('Employee'); @@ -881,12 +894,12 @@ describe('aggregate: ', function() { assert.equal(docs[0].sal, 18000); }); - describe('middleware (gh-5251)', function() { - it('pre', async function() { + describe('middleware (gh-5251)', function () { + it('pre', async function () { const s = new Schema({ name: String }); let called = 0; - s.pre('aggregate', function() { + s.pre('aggregate', function () { ++called; return Promise.resolve(); }); @@ -899,10 +912,10 @@ describe('aggregate: ', function() { assert.equal(called, 1); }); - it('setting option in pre (gh-7606)', async function() { + it('setting option in pre (gh-7606)', async function () { const s = new Schema({ name: String }); - s.pre('aggregate', function() { + s.pre('aggregate', function () { this.options.collation = { locale: 'en_US', strength: 1 }; return Promise.resolve(); }); @@ -917,10 +930,10 @@ describe('aggregate: ', function() { assert.equal(docs[1].name, 'Zeta'); }); - it('adding to pipeline in pre (gh-8017)', async function() { + it('adding to pipeline in pre (gh-8017)', async function () { const s = new Schema({ name: String }); - s.pre('aggregate', function() { + s.pre('aggregate', function () { this.append({ $limit: 1 }); return Promise.resolve(); }); @@ -935,11 +948,11 @@ describe('aggregate: ', function() { assert.equal(docs[0].name, 'Zeta'); }); - it('post', async function() { + it('post', async function () { const s = new Schema({ name: String }); const calledWith = []; - s.post('aggregate', function(res, next) { + s.post('aggregate', function (res, next) { calledWith.push(res); next(); }); @@ -953,11 +966,11 @@ describe('aggregate: ', function() { assert.deepEqual(calledWith[0], []); }); - it('error handler with agg error', async function() { + it('error handler with agg error', async function () { const s = new Schema({ name: String }); const calledWith = []; - s.post('aggregate', function(error, res, next) { + s.post('aggregate', function (error, res, next) { calledWith.push(error); next(); }); @@ -976,14 +989,14 @@ describe('aggregate: ', function() { assert.equal(calledWith[0], error); }); - it('error handler with pre error', async function() { + it('error handler with pre error', async function () { const s = new Schema({ name: String }); const calledWith = []; - s.pre('aggregate', function() { + s.pre('aggregate', function () { throw new Error('woops'); }); - s.post('aggregate', function(error, res, next) { + s.post('aggregate', function (error, res, next) { calledWith.push(error); next(); }); @@ -998,16 +1011,16 @@ describe('aggregate: ', function() { assert.equal(calledWith[0], error); }); - it('with agg cursor', async function() { + it('with agg cursor', async function () { const s = new Schema({ name: String }); let calledPre = 0; let calledPost = 0; - s.pre('aggregate', function() { + s.pre('aggregate', function () { ++calledPre; return Promise.resolve(); }); - s.post('aggregate', function(res, next) { + s.post('aggregate', function (res, next) { ++calledPost; next(); }); @@ -1018,23 +1031,23 @@ describe('aggregate: ', function() { await M. aggregate([{ $match: { name: 'test' } }]). cursor({ useMongooseAggCursor: true }). - eachAsync(function() { ++numDocs; }); + eachAsync(function () { ++numDocs; }); assert.equal(numDocs, 0); assert.equal(calledPre, 1); assert.equal(calledPost, 0); }); - it('with explain() (gh-5887)', function() { + it('with explain() (gh-5887)', function () { const s = new Schema({ name: String }); let calledPre = 0; const calledPost = []; - s.pre('aggregate', function() { + s.pre('aggregate', function () { ++calledPre; return Promise.resolve(); }); - s.post('aggregate', function(res, next) { + s.post('aggregate', function (res, next) { calledPost.push(res); next(); }); @@ -1050,7 +1063,7 @@ describe('aggregate: ', function() { }); }); - it('readPref from schema (gh-5522)', function() { + it('readPref from schema (gh-5522)', function () { const schema = new Schema({ name: String }, { read: 'secondary' }); const M = db.model('Test', schema); const a = M.aggregate(); @@ -1062,7 +1075,7 @@ describe('aggregate: ', function() { }); }); - it('cursor (gh-3160)', async function() { + it('cursor (gh-3160)', async function () { const MyModel = db.model('Test', { name: String }); await MyModel.create({ name: 'test' }); @@ -1075,7 +1088,7 @@ describe('aggregate: ', function() { assert.ok(cursor.eachAsync); }); - it('catch() (gh-7267)', async function() { + it('catch() (gh-7267)', async function () { const MyModel = db.model('Test', {}); const err = await MyModel.aggregate([{ $group: { foo: 'bar' } }]) @@ -1084,7 +1097,7 @@ describe('aggregate: ', function() { assert.equal(err.name, 'MongoServerError'); }); - it('cursor() without options (gh-3855)', function() { + it('cursor() without options (gh-3855)', function () { const MyModel = db.model('Test', { name: String }); const cursor = MyModel. @@ -1093,7 +1106,7 @@ describe('aggregate: ', function() { assert.ok(cursor instanceof require('stream').Readable); }); - it('cursor() with useMongooseAggCursor (gh-5145)', function() { + it('cursor() with useMongooseAggCursor (gh-5145)', function () { const MyModel = db.model('Test', { name: String }); const cursor = MyModel. @@ -1102,7 +1115,7 @@ describe('aggregate: ', function() { assert.ok(cursor instanceof require('stream').Readable); }); - it('cursor() with useMongooseAggCursor works (gh-5145) (gh-5394)', async function() { + it('cursor() with useMongooseAggCursor works (gh-5145) (gh-5394)', async function () { const MyModel = db.model('Test', { name: String }); await MyModel.create({ name: 'test' }); @@ -1111,7 +1124,7 @@ describe('aggregate: ', function() { await MyModel. aggregate([{ $match: { name: 'test' } }]). cursor({ useMongooseAggCursor: true }). - eachAsync(function(doc) { + eachAsync(function (doc) { docs.push(doc); }); @@ -1119,7 +1132,7 @@ describe('aggregate: ', function() { assert.equal(docs[0].name, 'test'); }); - it('cursor() eachAsync (gh-4300)', async function() { + it('cursor() eachAsync (gh-4300)', async function () { const MyModel = db.model('Test', { name: String }); let cur = 0; @@ -1129,12 +1142,12 @@ describe('aggregate: ', function() { await MyModel.aggregate([{ $sort: { name: 1 } }]). cursor(). - eachAsync(function(doc) { + eachAsync(function (doc) { const _cur = cur; assert.equal(doc.name, expectedNames[cur]); return { - then: function(resolve) { - setTimeout(function() { + then: function (resolve) { + setTimeout(function () { assert.equal(_cur, cur++); resolve(); }, 50); @@ -1143,18 +1156,18 @@ describe('aggregate: ', function() { }); }); - it('cursor() eachAsync with options (parallel)', async function() { + it('cursor() eachAsync with options (parallel)', async function () { const MyModel = db.model('Test', { name: String }); const names = []; const startedAt = []; const expectedNames = ['Axl', 'Slash']; - const checkDoc = function(doc) { + const checkDoc = function (doc) { names.push(doc.name); startedAt.push(Date.now()); return { - then: function(resolve) { - setTimeout(function() { + then: function (resolve) { + setTimeout(function () { resolve(); }, 100); } @@ -1165,7 +1178,7 @@ describe('aggregate: ', function() { await MyModel.aggregate([{ $sort: { name: 1 } }]). cursor(). - eachAsync(checkDoc, { parallel: 2 }).then(function() { + eachAsync(checkDoc, { parallel: 2 }).then(function () { assert.ok(Date.now() - startedAt[1] >= 75, Date.now() - startedAt[1]); assert.equal(startedAt.length, 2); assert.ok(startedAt[1] - startedAt[0] < 50, `${startedAt[1] - startedAt[0]}`); @@ -1173,14 +1186,14 @@ describe('aggregate: ', function() { }); }); - it('is now a proper aggregate cursor vs what it was before gh-10410', function() { + it('is now a proper aggregate cursor vs what it was before gh-10410', function () { const MyModel = db.model('Test', { name: String }); assert.throws(() => { MyModel.aggregate([]).cursor({ batchSize: 1000 }).exec(); }); }); - it('query by document (gh-4866)', async function() { + it('query by document (gh-4866)', async function () { const MyModel = db.model('Test', { name: String }); @@ -1190,7 +1203,7 @@ describe('aggregate: ', function() { assert.equal(res.length, 1); }); - it('sort by text score (gh-5258)', async function() { + it('sort by text score (gh-5258)', async function () { const mySchema = new Schema({ test: String }); mySchema.index({ test: 'text' }); const M = db.model('Test', mySchema); @@ -1210,7 +1223,7 @@ describe('aggregate: ', function() { assert.equal(res[1].test, 'a test'); }); - it('cursor supports transform option (gh-14331)', async function() { + it('cursor supports transform option (gh-14331)', async function () { const mySchema = new Schema({ name: String }); const Test = db.model('Test', mySchema); @@ -1236,12 +1249,12 @@ describe('aggregate: ', function() { assert.ok(streamValue.includes('"name":"Apple"'), streamValue); }); - describe('Mongo 3.6 options', function() { - before(async function() { + describe('Mongo 3.6 options', function () { + before(async function () { await onlyTestAtOrAbove('3.6', this); }); - it('adds hint option', async function() { + it('adds hint option', async function () { const mySchema = new Schema({ name: String, qty: Number }); mySchema.index({ qty: -1, name: -1 }); const M = db.model('Test', mySchema); @@ -1263,7 +1276,7 @@ describe('aggregate: ', function() { }); }); - it('should not throw error if database connection has not been established (gh-13125)', async function() { + it('should not throw error if database connection has not been established (gh-13125)', async function () { const m = new mongoose.Mongoose(); const mySchema = new Schema({ test: String }); const M = m.model('Test', mySchema); @@ -1279,7 +1292,7 @@ describe('aggregate: ', function() { await m.disconnect(); }); - it('throws error if calling near() with empty coordinates (gh-15188)', async function() { + it('throws error if calling near() with empty coordinates (gh-15188)', async function () { const M = db.model('Test', new Schema({ loc: { type: [Number], index: '2d' } })); assert.throws(() => { const aggregate = new Aggregate([], M); @@ -1292,10 +1305,10 @@ describe('aggregate: ', function() { }, /Aggregate `near\(\)` argument has invalid coordinates, got ""/); }); - it('cursor() errors out if schema pre aggregate hook throws an error (gh-15279)', async function() { + it('cursor() errors out if schema pre aggregate hook throws an error (gh-15279)', async function () { const schema = new Schema({ name: String }); - schema.pre('aggregate', function() { + schema.pre('aggregate', function () { if (!this.options.allowed) { throw new Error('Unauthorized aggregate operation: only allowed operations are permitted'); } @@ -1306,7 +1319,7 @@ describe('aggregate: ', function() { await Test.create({ name: 'test1' }); await assert.rejects( - async() => { + async () => { await Test.aggregate([{ $limit: 1 }], { allowed: false }).exec(); }, err => err.message === 'Unauthorized aggregate operation: only allowed operations are permitted' @@ -1314,7 +1327,7 @@ describe('aggregate: ', function() { const cursor = Test.aggregate([{ $limit: 1 }], { allowed: false }).cursor(); await assert.rejects( - async() => { + async () => { await cursor.next(); }, err => err.message === 'Unauthorized aggregate operation: only allowed operations are permitted' From 4412bee05b7000d98368183c1b045a256840c3dd Mon Sep 17 00:00:00 2001 From: Ankit Singh <155445436+singhankit001@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:09:03 +0000 Subject: [PATCH 2/4] Fixed requested changes --- lib/aggregate.js | 72 +++++++++--------- lib/helpers/query/castSort.js | 116 +++++++++++++++-------------- lib/query.js | 136 ++++++++++++++++------------------ 3 files changed, 159 insertions(+), 165 deletions(-) diff --git a/lib/aggregate.js b/lib/aggregate.js index 5ff659d12a3..bcf78b433b4 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -10,7 +10,7 @@ const Query = require('./query'); const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption'); const clone = require('./helpers/clone'); const castSort = require('./helpers/query/castSort'); -const getConstructorName = require('./helpers/getConstructorName'); + const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline'); const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators'); const utils = require('./utils'); @@ -100,7 +100,7 @@ Aggregate.prototype.options; * @api private */ -Aggregate.prototype._optionsForExec = function () { +Aggregate.prototype._optionsForExec = function() { const options = this.options || {}; const asyncLocalStorage = this.model()?.db?.base.transactionAsyncLocalStorage?.getStore(); @@ -128,7 +128,7 @@ Aggregate.prototype._optionsForExec = function () { * @api public */ -Aggregate.prototype.model = function (model) { +Aggregate.prototype.model = function(model) { if (arguments.length === 0) { return this._model; } @@ -164,7 +164,7 @@ Aggregate.prototype.model = function (model) { * @api public */ -Aggregate.prototype.append = function () { +Aggregate.prototype.append = function() { const args = (arguments.length === 1 && Array.isArray(arguments[0])) ? arguments[0] : [...arguments]; @@ -201,7 +201,7 @@ Aggregate.prototype.append = function () { * @return {Aggregate} * @api public */ -Aggregate.prototype.addFields = function (arg) { +Aggregate.prototype.addFields = function(arg) { if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) { throw new Error('Invalid addFields() argument. Must be an object'); } @@ -240,15 +240,15 @@ Aggregate.prototype.addFields = function (arg) { * @api public */ -Aggregate.prototype.project = function (arg) { +Aggregate.prototype.project = function(arg) { const fields = {}; if (typeof arg === 'object' && !Array.isArray(arg)) { - Object.keys(arg).forEach(function (field) { + Object.keys(arg).forEach(function(field) { fields[field] = arg[field]; }); } else if (arguments.length === 1 && typeof arg === 'string') { - arg.split(/\s+/).forEach(function (field) { + arg.split(/\s+/).forEach(function(field) { if (!field) { return; } @@ -403,7 +403,7 @@ Aggregate.prototype.project = function (arg) { * @api public */ -Aggregate.prototype.near = function (arg) { +Aggregate.prototype.near = function(arg) { if (arg == null) { throw new MongooseError('Aggregate `near()` must be called with non-nullish argument'); } @@ -424,8 +424,8 @@ Aggregate.prototype.near = function (arg) { * define methods */ -'group match skip limit out densify fill'.split(' ').forEach(function ($operator) { - Aggregate.prototype[$operator] = function (arg) { +'group match skip limit out densify fill'.split(' ').forEach(function($operator) { + Aggregate.prototype[$operator] = function(arg) { const op = {}; op['$' + $operator] = arg; return this.append(op); @@ -450,7 +450,7 @@ Aggregate.prototype.near = function (arg) { * @api public */ -Aggregate.prototype.unwind = function () { +Aggregate.prototype.unwind = function() { const args = [...arguments]; const res = []; @@ -489,7 +489,7 @@ Aggregate.prototype.unwind = function () { * @api public */ -Aggregate.prototype.replaceRoot = function (newRoot) { +Aggregate.prototype.replaceRoot = function(newRoot) { let ret; if (typeof newRoot === 'string') { @@ -518,7 +518,7 @@ Aggregate.prototype.replaceRoot = function (newRoot) { * @api public */ -Aggregate.prototype.count = function (fieldName) { +Aggregate.prototype.count = function(fieldName) { return this.append({ $count: fieldName }); }; @@ -540,7 +540,7 @@ Aggregate.prototype.count = function (fieldName) { * @api public */ -Aggregate.prototype.sortByCount = function (arg) { +Aggregate.prototype.sortByCount = function(arg) { if (arg && typeof arg === 'object') { return this.append({ $sortByCount: arg }); } else if (typeof arg === 'string') { @@ -566,7 +566,7 @@ Aggregate.prototype.sortByCount = function (arg) { * @api public */ -Aggregate.prototype.lookup = function (options) { +Aggregate.prototype.lookup = function(options) { return this.append({ $lookup: options }); }; @@ -586,7 +586,7 @@ Aggregate.prototype.lookup = function (options) { * @api public */ -Aggregate.prototype.graphLookup = function (options) { +Aggregate.prototype.graphLookup = function(options) { const cloneOptions = {}; if (options) { if (!utils.isObject(options)) { @@ -619,7 +619,7 @@ Aggregate.prototype.graphLookup = function (options) { * @api public */ -Aggregate.prototype.sample = function (size) { +Aggregate.prototype.sample = function(size) { return this.append({ $sample: { size: size } }); }; @@ -642,7 +642,7 @@ Aggregate.prototype.sample = function (size) { * @api public */ -Aggregate.prototype.sort = function (arg) { +Aggregate.prototype.sort = function(arg) { if (arguments.length > 1) { throw new TypeError('Invalid sort() argument. Must be a string or object.'); } @@ -664,7 +664,7 @@ Aggregate.prototype.sort = function (arg) { * @api public */ -Aggregate.prototype.unionWith = function (options) { +Aggregate.prototype.unionWith = function(options) { return this.append({ $unionWith: options }); }; @@ -683,7 +683,7 @@ Aggregate.prototype.unionWith = function (options) { * @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference */ -Aggregate.prototype.read = function (pref, tags) { +Aggregate.prototype.read = function(pref, tags) { read.call(this, pref, tags); return this; }; @@ -701,7 +701,7 @@ Aggregate.prototype.read = function (pref, tags) { * @api public */ -Aggregate.prototype.readConcern = function (level) { +Aggregate.prototype.readConcern = function(level) { readConcern.call(this, level); return this; }; @@ -733,7 +733,7 @@ Aggregate.prototype.readConcern = function (level) { * @api public */ -Aggregate.prototype.redact = function (expression, thenExpr, elseExpr) { +Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) { if (arguments.length === 3) { if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) || (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) { @@ -813,7 +813,7 @@ Aggregate.prototype.explain = async function explain(verbosity) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.allowDiskUse = function (value) { +Aggregate.prototype.allowDiskUse = function(value) { this.options.allowDiskUse = value; return this; }; @@ -830,7 +830,7 @@ Aggregate.prototype.allowDiskUse = function (value) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.hint = function (value) { +Aggregate.prototype.hint = function(value) { this.options.hint = value; return this; }; @@ -848,7 +848,7 @@ Aggregate.prototype.hint = function (value) { * @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/ */ -Aggregate.prototype.session = function (session) { +Aggregate.prototype.session = function(session) { if (session == null) { delete this.options.session; } else { @@ -875,7 +875,7 @@ Aggregate.prototype.session = function (session) { * @api public */ -Aggregate.prototype.option = function (value) { +Aggregate.prototype.option = function(value) { for (const key in value) { this.options[key] = value[key]; } @@ -902,7 +902,7 @@ Aggregate.prototype.option = function (value) { * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html */ -Aggregate.prototype.cursor = function (options) { +Aggregate.prototype.cursor = function(options) { this._optionsForExec(); this.options.cursor = options || {}; return new AggregationCursor(this); // return this; @@ -921,7 +921,7 @@ Aggregate.prototype.cursor = function (options) { * @see mongodb https://mongodb.github.io/node-mongodb-native/4.9/interfaces/CollationOptions.html */ -Aggregate.prototype.collation = function (collation) { +Aggregate.prototype.collation = function(collation) { this.options.collation = collation; return this; }; @@ -944,7 +944,7 @@ Aggregate.prototype.collation = function (collation) { * @api public */ -Aggregate.prototype.facet = function (options) { +Aggregate.prototype.facet = function(options) { return this.append({ $facet: options }); }; @@ -970,7 +970,7 @@ Aggregate.prototype.facet = function (options) { * @api public */ -Aggregate.prototype.search = function (options) { +Aggregate.prototype.search = function(options) { return this.append({ $search: options }); }; @@ -985,7 +985,7 @@ Aggregate.prototype.search = function (options) { * @api public */ -Aggregate.prototype.pipeline = function () { +Aggregate.prototype.pipeline = function() { return this._pipeline; }; @@ -1070,7 +1070,7 @@ Aggregate.prototype.exec = async function exec() { * @param {Function} [reject] errorCallback * @return {Promise} */ -Aggregate.prototype.then = function (resolve, reject) { +Aggregate.prototype.then = function(resolve, reject) { return this.exec().then(resolve, reject); }; @@ -1085,7 +1085,7 @@ Aggregate.prototype.then = function (resolve, reject) { * @api public */ -Aggregate.prototype.catch = function (reject) { +Aggregate.prototype.catch = function(reject) { return this.exec().then(null, reject); }; @@ -1100,7 +1100,7 @@ Aggregate.prototype.catch = function (reject) { * @api public */ -Aggregate.prototype.finally = function (onFinally) { +Aggregate.prototype.finally = function(onFinally) { return this.exec().finally(onFinally); }; @@ -1122,7 +1122,7 @@ Aggregate.prototype.finally = function (onFinally) { * @api public */ -Aggregate.prototype[Symbol.asyncIterator] = function () { +Aggregate.prototype[Symbol.asyncIterator] = function() { return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator(); }; diff --git a/lib/helpers/query/castSort.js b/lib/helpers/query/castSort.js index 65c1dc1ea4c..1e48a1a89a0 100644 --- a/lib/helpers/query/castSort.js +++ b/lib/helpers/query/castSort.js @@ -2,66 +2,72 @@ const specialProperties = require('../specialProperties'); +/** + * Casts a sort argument to a MongoDB sort object. + * + * @param {Object|String|Array|Map} arg The sort argument. + * @return {Object} The cast sort object. + */ module.exports = function castSort(arg) { - const sort = {}; + const sort = {}; - if (typeof arg === 'string') { - const properties = arg.indexOf(' ') === -1 ? [arg] : arg.split(' '); - for (let property of properties) { - if (!property) { - continue; - } - const ascend = '-' == property[0] ? -1 : 1; - if (ascend === -1) { - property = property.slice(1); - } - if (specialProperties.has(property)) { - continue; - } - sort[property] = ascend; - } - } else if (Array.isArray(arg)) { - for (const pair of arg) { - if (!Array.isArray(pair)) { - throw new TypeError('Invalid sort() argument, must be array of arrays'); - } - const key = '' + pair[0]; - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(pair[1], key); - } - } else if (typeof arg === 'object' && arg != null && !(arg instanceof Map)) { - for (const key of Object.keys(arg)) { - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(arg[key], key); - } - } else if (arg instanceof Map) { - for (let key of arg.keys()) { - key = '' + key; - if (specialProperties.has(key)) { - continue; - } - sort[key] = _handleSortValue(arg.get(key), key); - } - } else if (arg != null) { - throw new TypeError('Invalid sort() argument. Must be a string, object, array, or map.'); + if (typeof arg === 'string') { + const properties = arg.indexOf(' ') === -1 ? [arg] : arg.split(' '); + for (let property of properties) { + if (!property) { + continue; + } + const ascend = '-' == property[0] ? -1 : 1; + if (ascend === -1) { + property = property.slice(1); + } + if (specialProperties.has(property)) { + continue; + } + sort[property] = ascend; } + } else if (Array.isArray(arg)) { + for (const pair of arg) { + if (!Array.isArray(pair)) { + throw new TypeError('Invalid sort() argument, must be array of arrays'); + } + const key = '' + pair[0]; + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(pair[1], key); + } + } else if (typeof arg === 'object' && arg != null && !(arg instanceof Map)) { + for (const key of Object.keys(arg)) { + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(arg[key], key); + } + } else if (arg instanceof Map) { + for (let key of arg.keys()) { + key = '' + key; + if (specialProperties.has(key)) { + continue; + } + sort[key] = _handleSortValue(arg.get(key), key); + } + } else if (arg != null) { + throw new TypeError('Invalid sort() argument. Must be a string, object, array, or map.'); + } - return sort; + return sort; }; function _handleSortValue(val, key) { - if (val === 1 || val === 'asc' || val === 'ascending') { - return 1; - } - if (val === -1 || val === 'desc' || val === 'descending') { - return -1; - } - if (val?.$meta != null) { - return { $meta: val.$meta }; - } - throw new TypeError('Invalid sort value: { ' + key + ': ' + val + ' }'); + if (val === 1 || val === 'asc' || val === 'ascending') { + return 1; + } + if (val === -1 || val === 'desc' || val === 'descending') { + return -1; + } + if (val?.$meta != null) { + return { $meta: val.$meta }; + } + throw new TypeError('Invalid sort value: { ' + key + ': ' + val + ' }'); } diff --git a/lib/query.js b/lib/query.js index 945a6697280..7d6f72f510c 100644 --- a/lib/query.js +++ b/lib/query.js @@ -36,7 +36,7 @@ const sanitizeFilter = require('./helpers/query/sanitizeFilter'); const sanitizeProjection = require('./helpers/query/sanitizeProjection'); const selectPopulatedFields = require('./helpers/query/selectPopulatedFields'); const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert'); -const specialProperties = require('./helpers/specialProperties'); + const updateValidators = require('./helpers/updateValidators'); const util = require('util'); const utils = require('./utils'); @@ -275,7 +275,7 @@ Query.prototype.toConstructor = function toConstructor() { const model = this.model; const coll = this.mongooseCollection; - const CustomQuery = function (criteria, options) { + const CustomQuery = function(criteria, options) { if (!(this instanceof CustomQuery)) { return new CustomQuery(criteria, options); } @@ -332,7 +332,7 @@ Query.prototype.toConstructor = function toConstructor() { * @api public */ -Query.prototype.clone = function () { +Query.prototype.clone = function() { const model = this.model; const collection = this.mongooseCollection; @@ -458,7 +458,7 @@ Query.prototype.clone = function () { * @api public */ -Query.prototype.slice = function () { +Query.prototype.slice = function() { if (arguments.length === 0) { return this; } @@ -508,7 +508,7 @@ Query.prototype.slice = function () { const validOpsSet = new Set(queryMiddlewareFunctions); -Query.prototype._validateOp = function () { +Query.prototype._validateOp = function() { if (this.op != null && !validOpsSet.has(this.op)) { this.error(new Error('Query has invalid `op`: "' + this.op + '"')); } @@ -780,7 +780,7 @@ Query.prototype._validateOp = function () { * @api public */ -Query.prototype.mod = function () { +Query.prototype.mod = function() { let val; let path; @@ -1052,7 +1052,7 @@ Query.prototype.skip = function skip(v) { * @api public */ -Query.prototype.projection = function (arg) { +Query.prototype.projection = function(arg) { if (arguments.length === 0) { return this._fields; } @@ -1647,7 +1647,7 @@ Query.prototype.wtimeout = function wtimeout(ms) { * @api public */ -Query.prototype.getOptions = function () { +Query.prototype.getOptions = function() { return this.options; }; @@ -1707,7 +1707,7 @@ Query.prototype.getOptions = function () { * @api public */ -Query.prototype.setOptions = function (options, overwrite) { +Query.prototype.setOptions = function(options, overwrite) { // overwrite is only for internal use if (overwrite) { // ensure that _mongooseOptions & options are two different objects @@ -1867,7 +1867,7 @@ Query.prototype.explain = function explain(verbose) { * @api public */ -Query.prototype.allowDiskUse = function (v) { +Query.prototype.allowDiskUse = function(v) { if (arguments.length === 0) { this.options.allowDiskUse = true; } else if (v === false) { @@ -1897,7 +1897,7 @@ Query.prototype.allowDiskUse = function (v) { * @api public */ -Query.prototype.maxTimeMS = function (ms) { +Query.prototype.maxTimeMS = function(ms) { this.options.maxTimeMS = ms; return this; }; @@ -1915,7 +1915,7 @@ Query.prototype.maxTimeMS = function (ms) { * @api public */ -Query.prototype.getFilter = function () { +Query.prototype.getFilter = function() { return this._conditions; }; @@ -1935,7 +1935,7 @@ Query.prototype.getFilter = function () { * @api public */ -Query.prototype.getQuery = function () { +Query.prototype.getQuery = function() { return this._conditions; }; @@ -1954,7 +1954,7 @@ Query.prototype.getQuery = function () { * @api public */ -Query.prototype.setQuery = function (val) { +Query.prototype.setQuery = function(val) { this._conditions = val; }; @@ -1971,7 +1971,7 @@ Query.prototype.setQuery = function (val) { * @api public */ -Query.prototype.getUpdate = function () { +Query.prototype.getUpdate = function() { return this._update; }; @@ -1990,7 +1990,7 @@ Query.prototype.getUpdate = function () { * @api public */ -Query.prototype.setUpdate = function (val) { +Query.prototype.setUpdate = function(val) { this._update = val; }; @@ -2003,7 +2003,7 @@ Query.prototype.setUpdate = function (val) { * @memberOf Query */ -Query.prototype._fieldsForExec = function () { +Query.prototype._fieldsForExec = function() { if (this._fields == null) { return null; } @@ -2023,7 +2023,7 @@ Query.prototype._fieldsForExec = function () { * @memberOf Query */ -Query.prototype._updateForExec = function () { +Query.prototype._updateForExec = function() { const update = clone(this._update, { transform: false, depopulate: true @@ -2088,7 +2088,7 @@ Query.prototype._updateForExec = function () { * @api private */ -Query.prototype._optionsForExec = function (model) { +Query.prototype._optionsForExec = function(model) { const options = clone(this.options); delete options.populate; model = model || this.model; @@ -2198,7 +2198,7 @@ Query.prototype._optionsForExec = function (model) { * @api public */ -Query.prototype.lean = function (v) { +Query.prototype.lean = function(v) { this._mongooseOptions.lean = arguments.length ? v : true; return this; }; @@ -2220,7 +2220,7 @@ Query.prototype.lean = function (v) { * @api public */ -Query.prototype.set = function (path, val) { +Query.prototype.set = function(path, val) { if (typeof path === 'object') { const keys = Object.keys(path); for (const key of keys) { @@ -2348,7 +2348,7 @@ Query.prototype._unsetCastError = function _unsetCastError() { * @api public */ -Query.prototype.mongooseOptions = function (v) { +Query.prototype.mongooseOptions = function(v) { if (arguments.length > 0) { this._mongooseOptions = v; } @@ -2363,7 +2363,7 @@ Query.prototype.mongooseOptions = function (v) { * @instance */ -Query.prototype._castConditions = function () { +Query.prototype._castConditions = function() { let sanitizeFilterOpt = undefined; if (this.model?.db.options?.sanitizeFilter != null) { sanitizeFilterOpt = this.model.db.options.sanitizeFilter; @@ -2480,7 +2480,7 @@ Query.prototype._find = async function _find() { * @api public */ -Query.prototype.find = function (conditions) { +Query.prototype.find = function(conditions) { if (typeof conditions === 'function' || typeof arguments[1] === 'function') { throw new MongooseError('Query.prototype.find() no longer accepts a callback'); @@ -2508,7 +2508,7 @@ Query.prototype.find = function (conditions) { * @return {Query} this */ -Query.prototype.merge = function (source) { +Query.prototype.merge = function(source) { if (!source) { if (source === null) { this._conditions = null; @@ -2601,7 +2601,7 @@ Query.prototype.merge = function (source) { * @api public */ -Query.prototype.collation = function (value) { +Query.prototype.collation = function(value) { if (this.options == null) { this.options = {}; } @@ -2615,7 +2615,7 @@ Query.prototype.collation = function (value) { * @api private */ -Query.prototype._completeOne = function (doc, res, projection, callback) { +Query.prototype._completeOne = function(doc, res, projection, callback) { if (!doc && !this.options.includeResultMetadata) { return callback(null, null); } @@ -2759,7 +2759,7 @@ Query.prototype._findOne = async function _findOne() { * @api public */ -Query.prototype.findOne = function (conditions, projection, options) { +Query.prototype.findOne = function(conditions, projection, options) { if (typeof conditions === 'function' || typeof projection === 'function' || typeof options === 'function' || @@ -2887,7 +2887,7 @@ Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount * @api public */ -Query.prototype.estimatedDocumentCount = function (options) { +Query.prototype.estimatedDocumentCount = function(options) { if (typeof options === 'function' || typeof arguments[1] === 'function') { throw new MongooseError('Query.prototype.estimatedDocumentCount() no longer accepts a callback'); @@ -2940,7 +2940,7 @@ Query.prototype.estimatedDocumentCount = function (options) { * @api public */ -Query.prototype.countDocuments = function (conditions, options) { +Query.prototype.countDocuments = function(conditions, options) { if (typeof conditions === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { @@ -3004,7 +3004,7 @@ Query.prototype.__distinct = async function __distinct() { * @api public */ -Query.prototype.distinct = function (field, conditions, options) { +Query.prototype.distinct = function(field, conditions, options) { if (typeof field === 'function' || typeof conditions === 'function' || typeof options === 'function' || @@ -3065,7 +3065,7 @@ Query.prototype.distinct = function (field, conditions, options) { * @api public */ -Query.prototype.sort = function (arg, options) { +Query.prototype.sort = function(arg, options) { if (arguments.length > 2) { throw new Error('sort() takes at most 2 arguments'); } @@ -3090,18 +3090,6 @@ Query.prototype.sort = function (arg, options) { * Convert sort values */ -function _handleSortValue(val, key) { - if (val === 1 || val === 'asc' || val === 'ascending') { - return 1; - } - if (val === -1 || val === 'desc' || val === 'descending') { - return -1; - } - if (val?.$meta != null) { - return { $meta: val.$meta }; - } - throw new TypeError('Invalid sort value: { ' + key + ': ' + val + ' }'); -} /** * Declare and/or execute this query as a `deleteOne()` operation. Works like @@ -3212,7 +3200,7 @@ Query.prototype._deleteOne = async function _deleteOne() { * @api public */ -Query.prototype.deleteMany = function (filter, options) { +Query.prototype.deleteMany = function(filter, options) { if (typeof filter === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Query.prototype.deleteMany() no longer accepts a callback'); } @@ -3373,7 +3361,7 @@ function prepareDiscriminatorCriteria(query) { * @api public */ -Query.prototype.findOneAndUpdate = function (filter, update, options) { +Query.prototype.findOneAndUpdate = function(filter, update, options) { if (typeof filter === 'function' || typeof update === 'function' || typeof options === 'function' || @@ -3552,7 +3540,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { * @api public */ -Query.prototype.findOneAndDelete = function (filter, options) { +Query.prototype.findOneAndDelete = function(filter, options) { if (typeof filter === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { @@ -3653,7 +3641,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { * @api public */ -Query.prototype.findOneAndReplace = function (filter, replacement, options) { +Query.prototype.findOneAndReplace = function(filter, replacement, options) { if (typeof filter === 'function' || typeof replacement === 'function' || typeof options === 'function' || @@ -3783,7 +3771,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() { * @api public */ -Query.prototype.findById = function (id, projection, options) { +Query.prototype.findById = function(id, projection, options) { return this.findOne({ _id: id }, projection, options); }; @@ -3824,7 +3812,7 @@ Query.prototype.findById = function (id, projection, options) { * @api public */ -Query.prototype.findByIdAndUpdate = function (id, update, options) { +Query.prototype.findByIdAndUpdate = function(id, update, options) { return this.findOneAndUpdate({ _id: id }, update, options); }; @@ -3849,7 +3837,7 @@ Query.prototype.findByIdAndUpdate = function (id, update, options) { * @api public */ -Query.prototype.findByIdAndDelete = function (id, options) { +Query.prototype.findByIdAndDelete = function(id, options) { return this.findOneAndDelete({ _id: id }, options); }; @@ -3968,7 +3956,7 @@ function _completeManyLean(schema, docs, path, opts) { * @api private */ -Query.prototype._mergeUpdate = function (update) { +Query.prototype._mergeUpdate = function(update) { const updatePipeline = this._mongooseOptions.updatePipeline; if (!updatePipeline && Array.isArray(update)) { throw new MongooseError('Cannot pass an array to query updates unless the `updatePipeline` option is set.'); @@ -4166,7 +4154,7 @@ Query.prototype._replaceOne = async function _replaceOne() { * @api public */ -Query.prototype.updateMany = function (conditions, doc, options, callback) { +Query.prototype.updateMany = function(conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4241,7 +4229,7 @@ Query.prototype.updateMany = function (conditions, doc, options, callback) { * @api public */ -Query.prototype.updateOne = function (conditions, doc, options, callback) { +Query.prototype.updateOne = function(conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4308,7 +4296,7 @@ Query.prototype.updateOne = function (conditions, doc, options, callback) { * @api public */ -Query.prototype.replaceOne = function (conditions, doc, options, callback) { +Query.prototype.replaceOne = function(conditions, doc, options, callback) { if (typeof options === 'function') { // .update(conditions, doc, callback) callback = options; @@ -4412,7 +4400,7 @@ function _update(query, op, filter, doc, options, callback) { * @return {Query} this */ -Query.prototype.transform = function (fn) { +Query.prototype.transform = function(fn) { this._transforms.push(fn); return this; }; @@ -4447,7 +4435,7 @@ Query.prototype.transform = function (fn) { * @return {Query} this */ -Query.prototype.orFail = function (err) { +Query.prototype.orFail = function(err) { this.transform(res => { switch (this.op) { case 'find': @@ -4518,7 +4506,7 @@ function _orFailError(err, query) { * @api public */ -Query.prototype.isPathSelectedInclusive = function (path) { +Query.prototype.isPathSelectedInclusive = function(path) { return isPathSelectedInclusive(this._fields, path); }; @@ -4651,7 +4639,7 @@ function _executePreHooks(query, op) { * @api public */ -Query.prototype.then = function (resolve, reject) { +Query.prototype.then = function(resolve, reject) { return this.exec().then(resolve, reject); }; @@ -4667,7 +4655,7 @@ Query.prototype.then = function (resolve, reject) { * @api public */ -Query.prototype.catch = function (reject) { +Query.prototype.catch = function(reject) { return this.exec().then(null, reject); }; @@ -4682,7 +4670,7 @@ Query.prototype.catch = function (reject) { * @api public */ -Query.prototype.finally = function (onFinally) { +Query.prototype.finally = function(onFinally) { return this.exec().finally(onFinally); }; @@ -4726,7 +4714,7 @@ Query.prototype[Symbol.toStringTag] = function toString() { * @api public */ -Query.prototype.pre = function (fn) { +Query.prototype.pre = function(fn) { this._hooks.pre('exec', fn); return this; }; @@ -4752,7 +4740,7 @@ Query.prototype.pre = function (fn) { * @api public */ -Query.prototype.post = function (fn) { +Query.prototype.post = function(fn) { this._hooks.post('exec', fn); return this; }; @@ -4850,7 +4838,7 @@ Query.prototype._castUpdate = function _castUpdate(obj) { * @api public */ -Query.prototype.populate = function () { +Query.prototype.populate = function() { const args = Array.from(arguments); // Bail when given no truthy arguments if (!args.some(Boolean)) { @@ -4952,7 +4940,7 @@ function _getPopulatedPaths(list, arr, prefix) { * @api public */ -Query.prototype.cast = function (model, obj) { +Query.prototype.cast = function(model, obj) { obj || (obj = this._conditions); model = model || this.model; const discriminatorKey = model.schema.options.discriminatorKey; @@ -4998,10 +4986,10 @@ Query.prototype.cast = function (model, obj) { Query.prototype._castFields = function _castFields(fields) { let selected, - elemMatchKeys, - keys, - key, - out; + elemMatchKeys, + keys, + key, + out; if (fields) { keys = Object.keys(fields); @@ -5156,7 +5144,7 @@ Query.prototype.cursor = function cursor(opts) { * @api public */ -Query.prototype.tailable = function (val, opts) { +Query.prototype.tailable = function(val, opts) { // we need to support the tailable({ awaitData : true }) as well as the // tailable(true, {awaitData :true}) syntax that mquery does not support if (val != null && typeof val.constructor === 'function' && val.constructor.name === 'Object') { @@ -5297,7 +5285,7 @@ Query.prototype.tailable = function (val, opts) { * @api private */ -Query.prototype.near = function () { +Query.prototype.near = function() { const params = []; const sphere = this._mongooseOptions.nearSphere; @@ -5367,7 +5355,7 @@ Query.prototype.near = function () { * @see $maxDistance https://www.mongodb.com/docs/manual/reference/operator/maxDistance/ */ -Query.prototype.nearSphere = function () { +Query.prototype.nearSphere = function() { this._mongooseOptions.nearSphere = true; this.near.apply(this, arguments); return this; @@ -5450,7 +5438,7 @@ Query.prototype[Symbol.asyncIterator] = function queryAsyncIterator() { * @api private */ -Query.prototype.box = function (ll, ur) { +Query.prototype.box = function(ll, ur) { if (!Array.isArray(ll) && utils.isObject(ll)) { ur = ll.ur; ll = ll.ll; @@ -5520,7 +5508,7 @@ Query.prototype.center = Query.base.circle; * @api public */ -Query.prototype.centerSphere = function () { +Query.prototype.centerSphere = function() { if (arguments[0] != null && typeof arguments[0].constructor === 'function' && arguments[0].constructor.name === 'Object') { arguments[0].spherical = true; } From e15aa7ece3679c687d9eb53379762b0f0086fa11 Mon Sep 17 00:00:00 2001 From: Ankit Singh <155445436+singhankit001@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:29:38 +0000 Subject: [PATCH 3/4] fix: lint errors in aggregate.test.js --- test/aggregate.test.js | 308 ++++++++++++++++++++--------------------- 1 file changed, 154 insertions(+), 154 deletions(-) diff --git a/test/aggregate.test.js b/test/aggregate.test.js index 9636670fdd3..837eda2ce07 100644 --- a/test/aggregate.test.js +++ b/test/aggregate.test.js @@ -72,7 +72,7 @@ async function onlyTestAtOrAbove(semver, ctx) { * Test. */ -describe('aggregate: ', function () { +describe('aggregate: ', function() { let db; before(function startConnection() { @@ -87,8 +87,8 @@ describe('aggregate: ', function () { afterEach(() => require('./util').clearTestData(db)); afterEach(() => require('./util').stopRemainingOps(db)); - describe('append', function () { - it('(pipeline)', function () { + describe('append', function() { + it('(pipeline)', function() { const aggregate = new Aggregate(); assert.equal(aggregate.append({ $a: 1 }, { $b: 2 }, { $c: 3 }), aggregate); @@ -98,7 +98,7 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $a: 1 }, { $b: 2 }, { $c: 3 }, { $d: 4 }, { $c: 5 }]); }); - it('supports array as single argument', function () { + it('supports array as single argument', function() { const aggregate = new Aggregate(); assert.equal(aggregate.append([{ $a: 1 }, { $b: 2 }, { $c: 3 }]), aggregate); @@ -108,46 +108,46 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $a: 1 }, { $b: 2 }, { $c: 3 }, { $d: 4 }, { $c: 5 }]); }); - it('throws if non-operator parameter is passed', function () { + it('throws if non-operator parameter is passed', function() { const aggregate = new Aggregate(); const regexp = /Arguments must be aggregate pipeline operators/; - assert.throws(function () { + assert.throws(function() { aggregate.append({ $a: 1 }, 'string'); }, regexp); - assert.throws(function () { + assert.throws(function() { aggregate.append({ $a: 1 }, ['array']); }, regexp); - assert.throws(function () { + assert.throws(function() { aggregate.append({ $a: 1 }, { a: 1 }); }, regexp); - assert.throws(function () { + assert.throws(function() { aggregate.append([{ $a: 1 }, { a: 1 }]); }, regexp); }); - it('does not throw when 0 args passed', function () { + it('does not throw when 0 args passed', function() { const aggregate = new Aggregate(); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { aggregate.append(); }); }); - it('does not throw when empty array is passed as single argument', function () { + it('does not throw when empty array is passed as single argument', function() { const aggregate = new Aggregate(); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { aggregate.append([]); }); }); }); - describe('project', function () { - it('(object)', function () { + describe('project', function() { + it('(object)', function() { const aggregate = new Aggregate(); assert.equal(aggregate.project({ a: 1, b: 1, c: 0 }), aggregate); @@ -157,7 +157,7 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $project: { a: 1, b: 1, c: 0 } }, { $project: { b: 1 } }]); }); - it('(string)', function () { + it('(string)', function() { const aggregate = new Aggregate(); aggregate.project(' a b -c '); @@ -167,23 +167,23 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $project: { a: 1, b: 1, c: 0 } }, { $project: { b: 1 } }]); }); - it('("a","b","c")', function () { - assert.throws(function () { + it('("a","b","c")', function() { + assert.throws(function() { const aggregate = new Aggregate(); aggregate.project('a', 'b', 'c'); }, /Invalid project/); }); - it('["a","b","c"]', function () { - assert.throws(function () { + it('["a","b","c"]', function() { + assert.throws(function() { const aggregate = new Aggregate(); aggregate.project(['a', 'b', 'c']); }, /Invalid project/); }); }); - describe('group', function () { - it('works', function () { + describe('group', function() { + it('works', function() { const aggregate = new Aggregate(); assert.equal(aggregate.group({ a: 1, b: 2 }), aggregate); @@ -194,8 +194,8 @@ describe('aggregate: ', function () { }); }); - describe('skip', function () { - it('works', function () { + describe('skip', function() { + it('works', function() { const aggregate = new Aggregate(); assert.equal(aggregate.skip(42), aggregate); @@ -206,8 +206,8 @@ describe('aggregate: ', function () { }); }); - describe('limit', function () { - it('works', function () { + describe('limit', function() { + it('works', function() { const aggregate = new Aggregate(); assert.equal(aggregate.limit(42), aggregate); @@ -218,8 +218,8 @@ describe('aggregate: ', function () { }); }); - describe('unwind', function () { - it('("field")', function () { + describe('unwind', function() { + it('("field")', function() { const aggregate = new Aggregate(); assert.equal(aggregate.unwind('field'), aggregate); @@ -235,8 +235,8 @@ describe('aggregate: ', function () { }); }); - describe('match', function () { - it('works', function () { + describe('match', function() { + it('works', function() { const aggregate = new Aggregate(); assert.equal(aggregate.match({ a: 1 }), aggregate); @@ -247,8 +247,8 @@ describe('aggregate: ', function () { }); }); - describe('sort', function () { - it('(object)', function () { + describe('sort', function() { + it('(object)', function() { const aggregate = new Aggregate(); assert.equal(aggregate.sort({ a: 1, b: 'asc', c: 'descending' }), aggregate); @@ -258,7 +258,7 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $sort: { a: 1, b: 1, c: -1 } }, { $sort: { b: -1 } }]); }); - it('(string)', function () { + it('(string)', function() { const aggregate = new Aggregate(); aggregate.sort(' a b -c '); @@ -268,36 +268,36 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $sort: { a: 1, b: 1, c: -1 } }, { $sort: { b: 1 } }]); }); - it('("a","b","c")', function () { - assert.throws(function () { + it('("a","b","c")', function() { + assert.throws(function() { const aggregate = new Aggregate(); aggregate.sort('a', 'b', 'c'); }, /Invalid sort/); }); - it('["a","b","c"]', function () { - assert.throws(function () { + it('["a","b","c"]', function() { + assert.throws(function() { const aggregate = new Aggregate(); aggregate.sort(['a', 'b', 'c']); }, /Invalid sort/); }); - it('should support Maps for sort', function () { + it('should support Maps for sort', function() { const aggregate = new Aggregate(); const map = new Map([['name', 'asc'], ['age', -1]]); aggregate.sort(map); assert.deepEqual(aggregate._pipeline, [{ $sort: { name: 1, age: -1 } }]); }); - it('should support array of arrays for sort', function () { + it('should support array of arrays for sort', function() { const aggregate = new Aggregate(); aggregate.sort([['name', 'asc'], ['age', -1]]); assert.deepEqual(aggregate._pipeline, [{ $sort: { name: 1, age: -1 } }]); }); }); - describe('near', function () { - it('works', function () { + describe('near', function() { + it('works', function() { const aggregate = new Aggregate(); assert.equal(aggregate.near({ near: { type: 'Point', coordinates: [1, 2] } }), aggregate); @@ -310,7 +310,7 @@ describe('aggregate: ', function () { ]); }); - it('works with discriminators (gh-3304)', function () { + it('works with discriminators (gh-3304)', function() { let aggregate = new Aggregate(); const stub = { schema: { @@ -340,8 +340,8 @@ describe('aggregate: ', function () { }); }); - describe('lookup', function () { - it('works', function () { + describe('lookup', function() { + it('works', function() { const aggregate = new Aggregate(); const obj = { from: 'users', @@ -357,8 +357,8 @@ describe('aggregate: ', function () { }); }); - describe('unionWith', function () { - it('works', function () { + describe('unionWith', function() { + it('works', function() { const aggregate = new Aggregate(); const obj = { coll: 'users', @@ -376,8 +376,8 @@ describe('aggregate: ', function () { }); }); - describe('sample', function () { - it('works', function () { + describe('sample', function() { + it('works', function() { const aggregate = new Aggregate(); aggregate.sample(3); @@ -387,8 +387,8 @@ describe('aggregate: ', function () { }); }); - describe('densify', function () { - it('works', function () { + describe('densify', function() { + it('works', function() { const aggregate = new Aggregate(); const obj = { field: 'timestamp', @@ -406,8 +406,8 @@ describe('aggregate: ', function () { }); }); - describe('fill', function () { - it('works', function () { + describe('fill', function() { + it('works', function() { const aggregate = new Aggregate(); const obj = { output: @@ -425,8 +425,8 @@ describe('aggregate: ', function () { }); }); - describe('model()', function () { - it('works', function () { + describe('model()', function() { + it('works', function() { const aggregate = new Aggregate(); const model = { foo: 42 }; @@ -439,9 +439,9 @@ describe('aggregate: ', function () { }); }); - describe('redact', function () { + describe('redact', function() { const pipelineResult = [{ $redact: { $cond: { if: { $eq: ['$level', 5] }, then: '$$PRUNE', else: '$$DESCEND' } } }]; - it('works', function () { + it('works', function() { const aggregate = new Aggregate(); aggregate.redact({ $cond: { @@ -452,20 +452,20 @@ describe('aggregate: ', function () { }); assert.deepEqual(aggregate._pipeline, pipelineResult); }); - it('works with (condition, string, string)', function () { + it('works with (condition, string, string)', function() { const aggregate = new Aggregate(); aggregate.redact({ $eq: ['$level', 5] }, '$$PRUNE', '$$DESCEND'); assert.deepEqual(aggregate._pipeline, pipelineResult); }); }); - describe('Mongo 3.4 operators', function () { - before(async function () { + describe('Mongo 3.4 operators', function() { + before(async function() { await onlyTestAtOrAbove('3.4', this); }); - describe('graphLookup', function () { - it('works', function () { + describe('graphLookup', function() { + it('works', function() { const aggregate = new Aggregate(); aggregate.graphLookup({ startWith: '$test', @@ -483,7 +483,7 @@ describe('aggregate: ', function () { }); }); - it('automatically prepends $ to the startWith field', function () { + it('automatically prepends $ to the startWith field', function() { const aggregate = new Aggregate(); aggregate.graphLookup({ startWith: 'test' @@ -494,29 +494,29 @@ describe('aggregate: ', function () { }); }); - it('Throws if no options are passed to graphLookup', function () { + it('Throws if no options are passed to graphLookup', function() { const aggregate = new Aggregate(); - assert.throws(function () { + assert.throws(function() { aggregate.graphLookup('invalid options'); }, - TypeError); + TypeError); }); }); - describe('addFields', function () { - it('should throw if passed a non object', function () { + describe('addFields', function() { + it('should throw if passed a non object', function() { const aggregate = new Aggregate(); assert.throws(() => { aggregate.addFields('invalid'); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('should throw if passed null', function () { + it('should throw if passed null', function() { const aggregate = new Aggregate(); assert.throws(() => { aggregate.addFields(null); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('should throw if passed an Array', function () { + it('should throw if passed an Array', function() { const aggregate = new Aggregate(); assert.throws(() => { aggregate.addFields([]); }, /Invalid addFields\(\) argument\. Must be an object/); }); - it('(object)', function () { + it('(object)', function() { const aggregate = new Aggregate(); assert.equal(aggregate.addFields({ a: 1, b: 1, c: 0 }), aggregate); @@ -527,8 +527,8 @@ describe('aggregate: ', function () { }); }); - describe('facet', function () { - it('works', function () { + describe('facet', function() { + it('works', function() { const aggregate = new Aggregate(); aggregate.facet({ @@ -573,8 +573,8 @@ describe('aggregate: ', function () { }); }); - describe('replaceRoot', function () { - it('works with a string', function () { + describe('replaceRoot', function() { + it('works with a string', function() { const aggregate = new Aggregate(); aggregate.replaceRoot('myNewRoot'); @@ -582,7 +582,7 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $replaceRoot: { newRoot: '$myNewRoot' } }]); }); - it('works with an object (gh-6474)', function () { + it('works with an object (gh-6474)', function() { const aggregate = new Aggregate(); aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } }); @@ -592,8 +592,8 @@ describe('aggregate: ', function () { }); }); - describe('count', function () { - it('works', function () { + describe('count', function() { + it('works', function() { const aggregate = new Aggregate(); aggregate.count('countResult'); @@ -602,8 +602,8 @@ describe('aggregate: ', function () { }); }); - describe('sortByCount', function () { - it('works with a string argument', function () { + describe('sortByCount', function() { + it('works with a string argument', function() { const aggregate = new Aggregate(); aggregate.sortByCount('countedField'); @@ -611,7 +611,7 @@ describe('aggregate: ', function () { assert.deepEqual(aggregate._pipeline, [{ $sortByCount: '$countedField' }]); }); - it('works with an object argument', function () { + it('works with an object argument', function() { const aggregate = new Aggregate(); aggregate.sortByCount({ lname: '$employee.last' }); @@ -620,33 +620,33 @@ describe('aggregate: ', function () { [{ $sortByCount: { lname: '$employee.last' } }]); }); - it('throws if the argument is neither a string or object', function () { + it('throws if the argument is neither a string or object', function() { const aggregate = new Aggregate(); - assert.throws(function () { + assert.throws(function() { aggregate.sortByCount(1); }, TypeError); }); }); }); - describe('exec', function () { - beforeEach(async function () { + describe('exec', function() { + beforeEach(async function() { this.timeout(4000); // double the default of 2 seconds await setupData(db); }); - it('project', async function () { + it('project', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.project({ sal: 1, sal_k: { $divide: ['$sal', 1000] } }).exec(); - docs.forEach(function (doc) { + docs.forEach(function(doc) { assert.equal(doc.sal / 1000, doc.sal_k); }); }); - it('group', async function () { + it('group', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -660,7 +660,7 @@ describe('aggregate: ', function () { assert.notEqual(depts.indexOf('r&d'), -1); }); - it('skip', async function () { + it('skip', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -670,7 +670,7 @@ describe('aggregate: ', function () { assert.equal(docs.length, 3); }); - it('limit', async function () { + it('limit', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -680,7 +680,7 @@ describe('aggregate: ', function () { assert.equal(docs.length, 3); }); - it('unwind', async function () { + it('unwind', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -690,7 +690,7 @@ describe('aggregate: ', function () { assert.equal(docs.length, 5); }); - it('unwind with obj', function () { + it('unwind with obj', function() { const aggregate = new Aggregate(); const agg = aggregate. @@ -701,7 +701,7 @@ describe('aggregate: ', function () { true); }); - it('unwind throws with bad arg', function () { + it('unwind throws with bad arg', function() { const aggregate = new Aggregate(); let threw = false; @@ -715,7 +715,7 @@ describe('aggregate: ', function () { assert.ok(threw); }); - it('match', async function () { + it('match', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.match({ sal: { $gt: 15000 } }); @@ -723,7 +723,7 @@ describe('aggregate: ', function () { assert.equal(docs.length, 1); }); - it('sort', async function () { + it('sort', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate.sort('sal'); @@ -731,7 +731,7 @@ describe('aggregate: ', function () { assert.equal(docs[0].sal, 14000); }); - it('graphLookup', async function () { + it('graphLookup', async function() { const _this = this; const version = await start.mongodVersion(); @@ -764,7 +764,7 @@ describe('aggregate: ', function () { assert.equal(names[2], 'Carol'); }); - it('facet', async function () { + it('facet', async function() { const _this = this; const version = await start.mongodVersion(); @@ -802,7 +802,7 @@ describe('aggregate: ', function () { ]); }); - it('complex pipeline', async function () { + it('complex pipeline', async function() { const aggregate = new Aggregate([], db.model('Employee')); const docs = await aggregate. @@ -818,7 +818,7 @@ describe('aggregate: ', function () { assert.equal(docs[0].emp, 'Bob'); }); - it('pipeline() (gh-5825)', function () { + it('pipeline() (gh-5825)', function() { const aggregate = new Aggregate(); const pipeline = aggregate. @@ -828,7 +828,7 @@ describe('aggregate: ', function () { assert.deepEqual(pipeline, [{ $match: { sal: { $lt: 16000 } } }]); }); - it('explain()', async function () { + it('explain()', async function() { const aggregate = new Aggregate([], db.model('Employee')); const output = await aggregate. @@ -840,8 +840,8 @@ describe('aggregate: ', function () { assert.ok(output.stages || output.queryPlanner); }); - describe('error when empty pipeline', function () { - it('without a callback', function () { + describe('error when empty pipeline', function() { + it('without a callback', function() { const agg = new Aggregate([], db.model('Employee')); const promise = agg.exec(); @@ -854,8 +854,8 @@ describe('aggregate: ', function () { }); }); - describe('error when not bound to a model', function () { - it('with callback', async function () { + describe('error when not bound to a model', function() { + it('with callback', async function() { const aggregate = new Aggregate(); aggregate.skip(0); @@ -868,7 +868,7 @@ describe('aggregate: ', function () { }); }); - it('handles aggregation options', async function () { + it('handles aggregation options', async function() { const version = await start.mongodVersion(); const m = db.model('Employee'); @@ -894,12 +894,12 @@ describe('aggregate: ', function () { assert.equal(docs[0].sal, 18000); }); - describe('middleware (gh-5251)', function () { - it('pre', async function () { + describe('middleware (gh-5251)', function() { + it('pre', async function() { const s = new Schema({ name: String }); let called = 0; - s.pre('aggregate', function () { + s.pre('aggregate', function() { ++called; return Promise.resolve(); }); @@ -912,10 +912,10 @@ describe('aggregate: ', function () { assert.equal(called, 1); }); - it('setting option in pre (gh-7606)', async function () { + it('setting option in pre (gh-7606)', async function() { const s = new Schema({ name: String }); - s.pre('aggregate', function () { + s.pre('aggregate', function() { this.options.collation = { locale: 'en_US', strength: 1 }; return Promise.resolve(); }); @@ -930,10 +930,10 @@ describe('aggregate: ', function () { assert.equal(docs[1].name, 'Zeta'); }); - it('adding to pipeline in pre (gh-8017)', async function () { + it('adding to pipeline in pre (gh-8017)', async function() { const s = new Schema({ name: String }); - s.pre('aggregate', function () { + s.pre('aggregate', function() { this.append({ $limit: 1 }); return Promise.resolve(); }); @@ -948,11 +948,11 @@ describe('aggregate: ', function () { assert.equal(docs[0].name, 'Zeta'); }); - it('post', async function () { + it('post', async function() { const s = new Schema({ name: String }); const calledWith = []; - s.post('aggregate', function (res, next) { + s.post('aggregate', function(res, next) { calledWith.push(res); next(); }); @@ -966,11 +966,11 @@ describe('aggregate: ', function () { assert.deepEqual(calledWith[0], []); }); - it('error handler with agg error', async function () { + it('error handler with agg error', async function() { const s = new Schema({ name: String }); const calledWith = []; - s.post('aggregate', function (error, res, next) { + s.post('aggregate', function(error, res, next) { calledWith.push(error); next(); }); @@ -989,14 +989,14 @@ describe('aggregate: ', function () { assert.equal(calledWith[0], error); }); - it('error handler with pre error', async function () { + it('error handler with pre error', async function() { const s = new Schema({ name: String }); const calledWith = []; - s.pre('aggregate', function () { + s.pre('aggregate', function() { throw new Error('woops'); }); - s.post('aggregate', function (error, res, next) { + s.post('aggregate', function(error, res, next) { calledWith.push(error); next(); }); @@ -1011,16 +1011,16 @@ describe('aggregate: ', function () { assert.equal(calledWith[0], error); }); - it('with agg cursor', async function () { + it('with agg cursor', async function() { const s = new Schema({ name: String }); let calledPre = 0; let calledPost = 0; - s.pre('aggregate', function () { + s.pre('aggregate', function() { ++calledPre; return Promise.resolve(); }); - s.post('aggregate', function (res, next) { + s.post('aggregate', function(res, next) { ++calledPost; next(); }); @@ -1031,23 +1031,23 @@ describe('aggregate: ', function () { await M. aggregate([{ $match: { name: 'test' } }]). cursor({ useMongooseAggCursor: true }). - eachAsync(function () { ++numDocs; }); + eachAsync(function() { ++numDocs; }); assert.equal(numDocs, 0); assert.equal(calledPre, 1); assert.equal(calledPost, 0); }); - it('with explain() (gh-5887)', function () { + it('with explain() (gh-5887)', function() { const s = new Schema({ name: String }); let calledPre = 0; const calledPost = []; - s.pre('aggregate', function () { + s.pre('aggregate', function() { ++calledPre; return Promise.resolve(); }); - s.post('aggregate', function (res, next) { + s.post('aggregate', function(res, next) { calledPost.push(res); next(); }); @@ -1063,7 +1063,7 @@ describe('aggregate: ', function () { }); }); - it('readPref from schema (gh-5522)', function () { + it('readPref from schema (gh-5522)', function() { const schema = new Schema({ name: String }, { read: 'secondary' }); const M = db.model('Test', schema); const a = M.aggregate(); @@ -1075,7 +1075,7 @@ describe('aggregate: ', function () { }); }); - it('cursor (gh-3160)', async function () { + it('cursor (gh-3160)', async function() { const MyModel = db.model('Test', { name: String }); await MyModel.create({ name: 'test' }); @@ -1088,7 +1088,7 @@ describe('aggregate: ', function () { assert.ok(cursor.eachAsync); }); - it('catch() (gh-7267)', async function () { + it('catch() (gh-7267)', async function() { const MyModel = db.model('Test', {}); const err = await MyModel.aggregate([{ $group: { foo: 'bar' } }]) @@ -1097,7 +1097,7 @@ describe('aggregate: ', function () { assert.equal(err.name, 'MongoServerError'); }); - it('cursor() without options (gh-3855)', function () { + it('cursor() without options (gh-3855)', function() { const MyModel = db.model('Test', { name: String }); const cursor = MyModel. @@ -1106,7 +1106,7 @@ describe('aggregate: ', function () { assert.ok(cursor instanceof require('stream').Readable); }); - it('cursor() with useMongooseAggCursor (gh-5145)', function () { + it('cursor() with useMongooseAggCursor (gh-5145)', function() { const MyModel = db.model('Test', { name: String }); const cursor = MyModel. @@ -1115,7 +1115,7 @@ describe('aggregate: ', function () { assert.ok(cursor instanceof require('stream').Readable); }); - it('cursor() with useMongooseAggCursor works (gh-5145) (gh-5394)', async function () { + it('cursor() with useMongooseAggCursor works (gh-5145) (gh-5394)', async function() { const MyModel = db.model('Test', { name: String }); await MyModel.create({ name: 'test' }); @@ -1124,7 +1124,7 @@ describe('aggregate: ', function () { await MyModel. aggregate([{ $match: { name: 'test' } }]). cursor({ useMongooseAggCursor: true }). - eachAsync(function (doc) { + eachAsync(function(doc) { docs.push(doc); }); @@ -1132,7 +1132,7 @@ describe('aggregate: ', function () { assert.equal(docs[0].name, 'test'); }); - it('cursor() eachAsync (gh-4300)', async function () { + it('cursor() eachAsync (gh-4300)', async function() { const MyModel = db.model('Test', { name: String }); let cur = 0; @@ -1142,12 +1142,12 @@ describe('aggregate: ', function () { await MyModel.aggregate([{ $sort: { name: 1 } }]). cursor(). - eachAsync(function (doc) { + eachAsync(function(doc) { const _cur = cur; assert.equal(doc.name, expectedNames[cur]); return { - then: function (resolve) { - setTimeout(function () { + then: function(resolve) { + setTimeout(function() { assert.equal(_cur, cur++); resolve(); }, 50); @@ -1156,18 +1156,18 @@ describe('aggregate: ', function () { }); }); - it('cursor() eachAsync with options (parallel)', async function () { + it('cursor() eachAsync with options (parallel)', async function() { const MyModel = db.model('Test', { name: String }); const names = []; const startedAt = []; const expectedNames = ['Axl', 'Slash']; - const checkDoc = function (doc) { + const checkDoc = function(doc) { names.push(doc.name); startedAt.push(Date.now()); return { - then: function (resolve) { - setTimeout(function () { + then: function(resolve) { + setTimeout(function() { resolve(); }, 100); } @@ -1178,7 +1178,7 @@ describe('aggregate: ', function () { await MyModel.aggregate([{ $sort: { name: 1 } }]). cursor(). - eachAsync(checkDoc, { parallel: 2 }).then(function () { + eachAsync(checkDoc, { parallel: 2 }).then(function() { assert.ok(Date.now() - startedAt[1] >= 75, Date.now() - startedAt[1]); assert.equal(startedAt.length, 2); assert.ok(startedAt[1] - startedAt[0] < 50, `${startedAt[1] - startedAt[0]}`); @@ -1186,14 +1186,14 @@ describe('aggregate: ', function () { }); }); - it('is now a proper aggregate cursor vs what it was before gh-10410', function () { + it('is now a proper aggregate cursor vs what it was before gh-10410', function() { const MyModel = db.model('Test', { name: String }); assert.throws(() => { MyModel.aggregate([]).cursor({ batchSize: 1000 }).exec(); }); }); - it('query by document (gh-4866)', async function () { + it('query by document (gh-4866)', async function() { const MyModel = db.model('Test', { name: String }); @@ -1203,7 +1203,7 @@ describe('aggregate: ', function () { assert.equal(res.length, 1); }); - it('sort by text score (gh-5258)', async function () { + it('sort by text score (gh-5258)', async function() { const mySchema = new Schema({ test: String }); mySchema.index({ test: 'text' }); const M = db.model('Test', mySchema); @@ -1223,7 +1223,7 @@ describe('aggregate: ', function () { assert.equal(res[1].test, 'a test'); }); - it('cursor supports transform option (gh-14331)', async function () { + it('cursor supports transform option (gh-14331)', async function() { const mySchema = new Schema({ name: String }); const Test = db.model('Test', mySchema); @@ -1249,12 +1249,12 @@ describe('aggregate: ', function () { assert.ok(streamValue.includes('"name":"Apple"'), streamValue); }); - describe('Mongo 3.6 options', function () { - before(async function () { + describe('Mongo 3.6 options', function() { + before(async function() { await onlyTestAtOrAbove('3.6', this); }); - it('adds hint option', async function () { + it('adds hint option', async function() { const mySchema = new Schema({ name: String, qty: Number }); mySchema.index({ qty: -1, name: -1 }); const M = db.model('Test', mySchema); @@ -1276,7 +1276,7 @@ describe('aggregate: ', function () { }); }); - it('should not throw error if database connection has not been established (gh-13125)', async function () { + it('should not throw error if database connection has not been established (gh-13125)', async function() { const m = new mongoose.Mongoose(); const mySchema = new Schema({ test: String }); const M = m.model('Test', mySchema); @@ -1292,7 +1292,7 @@ describe('aggregate: ', function () { await m.disconnect(); }); - it('throws error if calling near() with empty coordinates (gh-15188)', async function () { + it('throws error if calling near() with empty coordinates (gh-15188)', async function() { const M = db.model('Test', new Schema({ loc: { type: [Number], index: '2d' } })); assert.throws(() => { const aggregate = new Aggregate([], M); @@ -1305,10 +1305,10 @@ describe('aggregate: ', function () { }, /Aggregate `near\(\)` argument has invalid coordinates, got ""/); }); - it('cursor() errors out if schema pre aggregate hook throws an error (gh-15279)', async function () { + it('cursor() errors out if schema pre aggregate hook throws an error (gh-15279)', async function() { const schema = new Schema({ name: String }); - schema.pre('aggregate', function () { + schema.pre('aggregate', function() { if (!this.options.allowed) { throw new Error('Unauthorized aggregate operation: only allowed operations are permitted'); } @@ -1319,7 +1319,7 @@ describe('aggregate: ', function () { await Test.create({ name: 'test1' }); await assert.rejects( - async () => { + async() => { await Test.aggregate([{ $limit: 1 }], { allowed: false }).exec(); }, err => err.message === 'Unauthorized aggregate operation: only allowed operations are permitted' @@ -1327,7 +1327,7 @@ describe('aggregate: ', function () { const cursor = Test.aggregate([{ $limit: 1 }], { allowed: false }).cursor(); await assert.rejects( - async () => { + async() => { await cursor.next(); }, err => err.message === 'Unauthorized aggregate operation: only allowed operations are permitted' From 4c1bc94f51ab2c1747623f80d74ce36f4618c8c9 Mon Sep 17 00:00:00 2001 From: Ankit Singh <155445436+singhankit001@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:25:24 +0530 Subject: [PATCH 4/4] style(query): use strict equality and avoid optional chaining in castSort --- lib/helpers/query/castSort.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/helpers/query/castSort.js b/lib/helpers/query/castSort.js index 1e48a1a89a0..21184ee73a0 100644 --- a/lib/helpers/query/castSort.js +++ b/lib/helpers/query/castSort.js @@ -1,4 +1,4 @@ -'use strict'; +"use strict"; const specialProperties = require('../specialProperties'); @@ -17,7 +17,7 @@ module.exports = function castSort(arg) { if (!property) { continue; } - const ascend = '-' == property[0] ? -1 : 1; + const ascend = '-' === property[0] ? -1 : 1; if (ascend === -1) { property = property.slice(1); } @@ -66,8 +66,8 @@ function _handleSortValue(val, key) { if (val === -1 || val === 'desc' || val === 'descending') { return -1; } - if (val?.$meta != null) { + if (val && val.$meta != null) { return { $meta: val.$meta }; } throw new TypeError('Invalid sort value: { ' + key + ': ' + val + ' }'); -} +} \ No newline at end of file