diff --git a/.eslintrc.js b/.eslintrc.js index 5609b0fae89..9d067d9de23 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,9 @@ module.exports = { '**/*.md/*.ts', '**/*.md/*.typescript' ], + parserOptions: { + project: './tsconfig.json' + }, extends: [ 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5abb3c2bb00..e22404cb71f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - name: Setup node uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: 14 + node-version: 18 - run: npm install @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - node: [14, 16, 18, 20] + node: [16, 18, 20] os: [ubuntu-20.04, ubuntu-22.04] mongodb: [4.4.18, 5.0.14, 6.0.4] include: @@ -108,7 +108,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v1.34.x + deno-version: v1.36.x - run: deno --version - run: npm install - name: Run Deno tests diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index 5103127bec8..ca925ddc195 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -27,7 +27,7 @@ jobs: - name: Setup node uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: - node-version: 14 + node-version: 18 - run: npm install diff --git a/CHANGELOG.md b/CHANGELOG.md index f88d2188661..122ecdfdae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +7.5.1 / 2023-09-11 +================== + * fix: set default value for _update when no update object is provided and versionKey is set to false #13795 #13783 [MohOraby](https://github.com/MohOraby) + * fix: avoid unexpected error when accessing null array element on discriminator array when populating #13716 [ZSabakh](https://github.com/ZSabakh) + * types(schematypes): use DocType for instance method this #13822 #13800 [pshaddel](https://github.com/pshaddel) + * types: remove duplicated 'exists' method in Model interface in models.d.ts #13818 [ohzeno](https://github.com/ohzeno) + * docs(model): replace outdated docs on deprecated findOneAndUpdate() overwrite option #13821 #13715 + * docs: add example of using `virtuals.pathsToSkip` option for `toObject()` and `toJSON()` #13798 [RobertHunter-Pluto](https://github.com/RobertHunter-Pluto) + 7.5.0 / 2023-08-29 ================== * feat: use mongodb driver v5.18.1 diff --git a/docs/middleware.md b/docs/middleware.md index 433e1fb149d..923379ff9f2 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -48,7 +48,6 @@ In query middleware functions, `this` refers to the query. * [find](api/query.html#query_Query-find) * [findOne](api/query.html#query_Query-findOne) * [findOneAndDelete](api/query.html#query_Query-findOneAndDelete) -* [findOneAndRemove](api/query.html#query_Query-findOneAndRemove) * [findOneAndReplace](api/query.html#query_Query-findOneAndReplace) * [findOneAndUpdate](api/query.html#query_Query-findOneAndUpdate) * [remove](api/model.html#model_Model-remove) @@ -81,7 +80,6 @@ Here are the possible strings that can be passed to `pre()` * find * findOne * findOneAndDelete -* findOneAndRemove * findOneAndReplace * findOneAndUpdate * init diff --git a/docs/migrating_to_8.md b/docs/migrating_to_8.md new file mode 100644 index 00000000000..b9c8cf1346a --- /dev/null +++ b/docs/migrating_to_8.md @@ -0,0 +1,62 @@ +# Migrating from 7.x to 8.x + + + +There are several backwards-breaking changes +you should be aware of when migrating from Mongoose 7.x to Mongoose 8.x. + +If you're still on Mongoose 6.x or earlier, please read the [Mongoose 6.x to 7.x migration guide](migrating_to_7.html) and upgrade to Mongoose 7.x first before upgrading to Mongoose 8. + +* [Removed `rawResult` option for `findOneAndUpdate()`](#removed-rawresult-option-for-findoneandupdate) +* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert) +* [MongoDB Node Driver 6.0](#mongodb-node-driver-6) +* [Removed `findOneAndRemove()`](#removed-findoneandremove) +* [Removed id Setter](#removed-id-setter) + +

Removed rawResult option for findOneAndUpdate()

+ +The `rawResult` option for `findOneAndUpdate()`, `findOneAndReplace()`, and `findOneAndDelete()` has been replaced by the `includeResultMetadata` option. + +```javascript +const filter = { name: 'Will Riker' }; +const update = { age: 29 }; + +const res = await Character.findOneAndUpdate(filter, update, { + new: true, + upsert: true, + // Replace `rawResult: true` with `includeResultMetadata: true` + includeResultMetadata: true +}); +``` + +`includeResultMetadata` in Mongoose 8 behaves identically to `rawResult`. + +

Changed behavior for findOneAndUpdate() with orFail() and upsert

+ +In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted. +In other words, `findOneAndUpdate().orFail()` always threw an error if no document was found, even if a new document was upserted. + +In Mongoose 8, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` always succeeds. +`findOneAndUpdate().orFail()` now throws a `DocumentNotFoundError` if there's no document returned, rather than if no document was found. + +

MongoDB Node Driver 6

+ +Mongoose 8 uses [v6.x of the MongoDB Node driver](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md#600-2023-08-28). +There's a few noteable changes in MongoDB Node driver v6 that affect Mongoose: + +1. The `ObjectId` constructor no longer accepts strings of length 12. In Mongoose 7, `new mongoose.Types.ObjectId('12charstring')` was perfectly valid. In Mongoose 8, `new mongoose.Types.ObjectId('12charstring')` throws an error. + +

Removed findOneAndRemove()

+ +In Mongoose 7, `findOneAndRemove()` was an alias for `findOneAndDelete()` that Mongoose supported for backwards compatibility. +Mongoose 8 no longer supports `findOneAndRemove()`. +Use `findOneAndDelete()` instead. + +

Removed id Setter

+ +In Mongoose 7.4, Mongoose introduced an `id` setter that made `doc.id = '0'.repeat(24)` equivalent to `doc._id = '0'.repeat(24)`. +In Mongoose 8, that setter is now removed. \ No newline at end of file diff --git a/docs/models.md b/docs/models.md index b046303aa06..84c53cca0d1 100644 --- a/docs/models.md +++ b/docs/models.md @@ -193,7 +193,7 @@ If you attempt to `save()` a document from a View, you will get an error from th ## Yet more -The [API docs](api/model.html#model_Model) cover many additional methods available like [count](api/model.html#model_Model-count), [mapReduce](api/model.html#model_Model-mapReduce), [aggregate](api/model.html#model_Model-aggregate), and [more](api/model.html#model_Model-findOneAndRemove). +The [API docs](api/model.html#model_Model) cover many additional methods available like [count](api/model.html#model_Model-count), [mapReduce](api/model.html#model_Model-mapReduce), [aggregate](api/model.html#model_Model-aggregate), and more. ## Next Up diff --git a/docs/queries.md b/docs/queries.md index 8ca6cf1345a..27936197f6a 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -14,7 +14,6 @@ Each of these functions returns a * [`Model.findByIdAndUpdate()`](api.html#model_Model-findByIdAndUpdate) * [`Model.findOne()`](api.html#model_Model-findOne) * [`Model.findOneAndDelete()`](api.html#model_Model-findOneAndDelete) -* [`Model.findOneAndRemove()`](api.html#model_Model-findOneAndRemove) * [`Model.findOneAndReplace()`](api.html#model_Model-findOneAndReplace) * [`Model.findOneAndUpdate()`](api.html#model_Model-findOneAndUpdate) * [`Model.replaceOne()`](api.html#model_Model-replaceOne) diff --git a/docs/source/api.js b/docs/source/api.js index 5c13d003d44..3aad4c04c2a 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -18,16 +18,16 @@ const files = [ 'lib/cursor/QueryCursor.js', 'lib/aggregate.js', 'lib/cursor/AggregationCursor.js', - 'lib/schematype.js', - 'lib/virtualtype.js', + 'lib/schemaType.js', + 'lib/virtualType.js', 'lib/error/index.js', 'lib/schema/array.js', - 'lib/schema/documentarray.js', - 'lib/schema/SubdocumentPath.js', + 'lib/schema/documentArray.js', + 'lib/schema/subdocument.js', 'lib/schema/boolean.js', 'lib/schema/buffer.js', 'lib/schema/number.js', - 'lib/schema/objectid.js', + 'lib/schema/objectId.js', 'lib/schema/string.js', 'lib/options/SchemaTypeOptions.js', 'lib/options/SchemaArrayOptions.js', diff --git a/docs/source/index.js b/docs/source/index.js index 1e5aaf50a6c..138853ff93a 100644 --- a/docs/source/index.js +++ b/docs/source/index.js @@ -65,6 +65,7 @@ docs['docs/migration.md'] = { guide: true, title: 'Migration Guide', markdown: t docs['docs/migrating_to_5.md'] = { guide: true, title: 'Migrating to Mongoose 5', markdown: true }; docs['docs/migrating_to_6.md'] = { guide: true, title: 'Migrating to Mongoose 6', markdown: true }; docs['docs/migrating_to_7.md'] = { guide: true, title: 'Migrating to Mongoose 7', markdown: true }; +docs['docs/migrating_to_8.md'] = { guide: true, title: 'Migrating to Mongoose 8', markdown: true }; docs['docs/connections.md'] = { guide: true, title: 'Connecting to MongoDB', markdown: true }; docs['docs/lambda.md'] = { guide: true, title: 'Using Mongoose With AWS Lambda', markdown: true }; docs['docs/geojson.md'] = { guide: true, title: 'Using GeoJSON', acquit: true, markdown: true }; diff --git a/lib/browser.js b/lib/browser.js index aef666b85a0..12b0cbde653 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -4,7 +4,7 @@ require('./driver').set(require('./drivers/browser')); -const DocumentProvider = require('./document_provider.js'); +const DocumentProvider = require('./documentProvider.js'); DocumentProvider.setBrowser(true); @@ -67,7 +67,7 @@ exports.Types = require('./types'); * @method VirtualType * @api public */ -exports.VirtualType = require('./virtualtype'); +exports.VirtualType = require('./virtualType'); /** * The various Mongoose SchemaTypes. @@ -81,7 +81,7 @@ exports.VirtualType = require('./virtualtype'); * @api public */ -exports.SchemaType = require('./schematype.js'); +exports.SchemaType = require('./schemaType.js'); /** * Internal utils diff --git a/lib/collection.js b/lib/collection.js index 35ddc922ef1..117a8c69551 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -5,7 +5,7 @@ */ const EventEmitter = require('events').EventEmitter; -const STATES = require('./connectionstate'); +const STATES = require('./connectionState'); const immediate = require('./helpers/immediate'); /** diff --git a/lib/connection.js b/lib/connection.js index 78b69c60c77..e7cf8302e87 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -7,7 +7,7 @@ const ChangeStream = require('./cursor/ChangeStream'); const EventEmitter = require('events').EventEmitter; const Schema = require('./schema'); -const STATES = require('./connectionstate'); +const STATES = require('./connectionState'); const MongooseError = require('./error/index'); const ServerSelectionError = require('./error/serverSelection'); const SyncIndexesError = require('./error/syncIndexes'); diff --git a/lib/connectionstate.js b/lib/connectionState.js similarity index 100% rename from lib/connectionstate.js rename to lib/connectionState.js diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/QueryCursor.js index f59041eb51b..0d0389747e5 100644 --- a/lib/cursor/QueryCursor.js +++ b/lib/cursor/QueryCursor.js @@ -7,7 +7,7 @@ const MongooseError = require('../error/mongooseError'); const Readable = require('stream').Readable; const eachAsync = require('../helpers/cursor/eachAsync'); -const helpers = require('../queryhelpers'); +const helpers = require('../queryHelpers'); const kareem = require('kareem'); const immediate = require('../helpers/immediate'); const util = require('util'); diff --git a/lib/document.js b/lib/document.js index 773f4749a16..c31afcaca52 100644 --- a/lib/document.js +++ b/lib/document.js @@ -35,7 +35,7 @@ const internalToObjectOptions = require('./options').internalToObjectOptions; const markArraySubdocsPopulated = require('./helpers/populate/markArraySubdocsPopulated'); const minimize = require('./helpers/minimize'); const mpath = require('mpath'); -const queryhelpers = require('./queryhelpers'); +const queryhelpers = require('./queryHelpers'); const utils = require('./utils'); const isPromise = require('./helpers/isPromise'); diff --git a/lib/document_provider.js b/lib/documentProvider.js similarity index 90% rename from lib/document_provider.js rename to lib/documentProvider.js index 1ace61f4fb3..894494403f4 100644 --- a/lib/document_provider.js +++ b/lib/documentProvider.js @@ -15,7 +15,7 @@ let isBrowser = false; * * @api private */ -module.exports = function() { +module.exports = function documentProvider() { if (isBrowser) { return BrowserDocument; } diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js index 0e6a2c29836..6898d602e12 100644 --- a/lib/drivers/node-mongodb-native/connection.js +++ b/lib/drivers/node-mongodb-native/connection.js @@ -6,7 +6,7 @@ const MongooseConnection = require('../../connection'); const MongooseError = require('../../error/index'); -const STATES = require('../../connectionstate'); +const STATES = require('../../connectionState'); const mongodb = require('mongodb'); const pkg = require('../../../package.json'); const processConnectionOptions = require('../../helpers/processConnectionOptions'); diff --git a/lib/helpers/discriminator/mergeDiscriminatorSchema.js b/lib/helpers/discriminator/mergeDiscriminatorSchema.js index fcba6c231c4..b48d5c4db07 100644 --- a/lib/helpers/discriminator/mergeDiscriminatorSchema.js +++ b/lib/helpers/discriminator/mergeDiscriminatorSchema.js @@ -34,7 +34,8 @@ module.exports = function mergeDiscriminatorSchema(to, from, path, seen) { key === 'base' || key === '_applyDiscriminators' || key === '_userProvidedOptions' || - key === 'options') { + key === 'options' || + key === 'tree') { continue; } } @@ -73,4 +74,8 @@ module.exports = function mergeDiscriminatorSchema(to, from, path, seen) { mergeDiscriminatorSchema(to[key], from[key], path ? path + '.' + key : key, seen); } } + + if (from != null && from.instanceOfSchema) { + to.tree = Object.assign({}, from.tree, to.tree); + } }; diff --git a/lib/helpers/model/applyHooks.js b/lib/helpers/model/applyHooks.js index 7ed7895d4b3..998da62f42a 100644 --- a/lib/helpers/model/applyHooks.js +++ b/lib/helpers/model/applyHooks.js @@ -64,7 +64,7 @@ function applyHooks(model, schema, options) { continue; } - applyHooks(childModel, type.schema, options); + applyHooks(childModel, type.schema, { ...options, isChildSchema: true }); if (childModel.discriminators != null) { const keys = Object.keys(childModel.discriminators); for (const key of keys) { @@ -104,7 +104,8 @@ function applyHooks(model, schema, options) { objToDecorate.$__originalValidate = objToDecorate.$__originalValidate || objToDecorate.$__validate; - for (const method of ['save', 'validate', 'remove', 'deleteOne']) { + const internalMethodsToWrap = options && options.isChildSchema ? ['save', 'validate', 'deleteOne'] : ['save', 'validate']; + for (const method of internalMethodsToWrap) { const toWrap = method === 'validate' ? '$__originalValidate' : `$__${method}`; const wrapped = middleware. createWrapper(method, objToDecorate[toWrap], null, kareemOptions); diff --git a/lib/helpers/populate/assignRawDocsToIdStructure.js b/lib/helpers/populate/assignRawDocsToIdStructure.js index c08671e5918..6a0cc06653c 100644 --- a/lib/helpers/populate/assignRawDocsToIdStructure.js +++ b/lib/helpers/populate/assignRawDocsToIdStructure.js @@ -7,7 +7,7 @@ const utils = require('../../utils'); module.exports = assignRawDocsToIdStructure; -const kHasArray = Symbol('assignRawDocsToIdStructure.hasArray'); +const kHasArray = Symbol('mongoose#assignRawDocsToIdStructure#hasArray'); /** * Assign `vals` returned by mongo query to the `rawIds` diff --git a/lib/helpers/promiseOrCallback.js b/lib/helpers/promiseOrCallback.js index 585a6486f2e..952eecf4bf8 100644 --- a/lib/helpers/promiseOrCallback.js +++ b/lib/helpers/promiseOrCallback.js @@ -2,7 +2,7 @@ const immediate = require('./immediate'); -const emittedSymbol = Symbol('mongoose:emitted'); +const emittedSymbol = Symbol('mongoose#emitted'); module.exports = function promiseOrCallback(callback, fn, ee, Promise) { if (typeof callback === 'function') { diff --git a/lib/helpers/query/completeMany.js b/lib/helpers/query/completeMany.js index 6fab7636677..a27ecafa385 100644 --- a/lib/helpers/query/completeMany.js +++ b/lib/helpers/query/completeMany.js @@ -1,6 +1,6 @@ 'use strict'; -const helpers = require('../../queryhelpers'); +const helpers = require('../../queryHelpers'); module.exports = completeMany; diff --git a/lib/helpers/query/validOps.js b/lib/helpers/query/validOps.js index f89c84264a2..2eb4375a93f 100644 --- a/lib/helpers/query/validOps.js +++ b/lib/helpers/query/validOps.js @@ -2,7 +2,6 @@ module.exports = Object.freeze([ // Read - 'count', 'countDocuments', 'distinct', 'estimatedDocumentCount', @@ -17,6 +16,5 @@ module.exports = Object.freeze([ // Delete 'deleteMany', 'deleteOne', - 'findOneAndDelete', - 'findOneAndRemove' + 'findOneAndDelete' ]); diff --git a/lib/helpers/schema/idGetter.js b/lib/helpers/schema/idGetter.js index 6df8a8cc04f..1fe7cc77ab3 100644 --- a/lib/helpers/schema/idGetter.js +++ b/lib/helpers/schema/idGetter.js @@ -16,7 +16,6 @@ module.exports = function addIdGetter(schema) { return schema; } schema.virtual('id').get(idGetter); - schema.virtual('id').set(idSetter); return schema; }; @@ -33,14 +32,3 @@ function idGetter() { return null; } - -/** - * - * @param {String} v the id to set - * @api private - */ - -function idSetter(v) { - this._id = v; - return v; -} diff --git a/lib/helpers/symbols.js b/lib/helpers/symbols.js index f12db3d8272..a9af890a5d6 100644 --- a/lib/helpers/symbols.js +++ b/lib/helpers/symbols.js @@ -5,7 +5,7 @@ exports.arrayAtomicsSymbol = Symbol('mongoose#Array#_atomics'); exports.arrayParentSymbol = Symbol('mongoose#Array#_parent'); exports.arrayPathSymbol = Symbol('mongoose#Array#_path'); exports.arraySchemaSymbol = Symbol('mongoose#Array#_schema'); -exports.documentArrayParent = Symbol('mongoose:documentArrayParent'); +exports.documentArrayParent = Symbol('mongoose#documentArrayParent'); exports.documentIsSelected = Symbol('mongoose#Document#isSelected'); exports.documentIsModified = Symbol('mongoose#Document#isModified'); exports.documentModifiedPaths = Symbol('mongoose#Document#modifiedPaths'); @@ -13,8 +13,8 @@ exports.documentSchemaSymbol = Symbol('mongoose#Document#schema'); exports.getSymbol = Symbol('mongoose#Document#get'); exports.modelSymbol = Symbol('mongoose#Model'); exports.objectIdSymbol = Symbol('mongoose#ObjectId'); -exports.populateModelSymbol = Symbol('mongoose.PopulateOptions#Model'); +exports.populateModelSymbol = Symbol('mongoose#PopulateOptions#Model'); exports.schemaTypeSymbol = Symbol('mongoose#schemaType'); -exports.sessionNewDocuments = Symbol('mongoose:ClientSession#newDocuments'); +exports.sessionNewDocuments = Symbol('mongoose#ClientSession#newDocuments'); exports.scopeSymbol = Symbol('mongoose#Document#scope'); -exports.validatorErrorSymbol = Symbol('mongoose:validatorError'); +exports.validatorErrorSymbol = Symbol('mongoose#validatorError'); diff --git a/lib/index.js b/lib/index.js index 6c551ae5dfa..d251828e4f3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,11 +10,11 @@ const Document = require('./document'); const EventEmitter = require('events').EventEmitter; const Kareem = require('kareem'); const Schema = require('./schema'); -const SchemaType = require('./schematype'); +const SchemaType = require('./schemaType'); const SchemaTypes = require('./schema/index'); -const VirtualType = require('./virtualtype'); -const STATES = require('./connectionstate'); -const VALID_OPTIONS = require('./validoptions'); +const VirtualType = require('./virtualType'); +const STATES = require('./connectionState'); +const VALID_OPTIONS = require('./validOptions'); const Types = require('./types'); const Query = require('./query'); const Model = require('./model'); @@ -981,7 +981,7 @@ Mongoose.prototype.Document = Document; * @api public */ -Mongoose.prototype.DocumentProvider = require('./document_provider'); +Mongoose.prototype.DocumentProvider = require('./documentProvider'); /** * The Mongoose ObjectId [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for diff --git a/lib/internal.js b/lib/internal.js index c4445c254d6..8de1b278773 100644 --- a/lib/internal.js +++ b/lib/internal.js @@ -4,7 +4,7 @@ 'use strict'; -const StateMachine = require('./statemachine'); +const StateMachine = require('./stateMachine'); const ActiveRoster = StateMachine.ctor('require', 'modify', 'init', 'default', 'ignore'); module.exports = exports = InternalCache; diff --git a/lib/model.js b/lib/model.js index b01464ad296..3a67d4578e8 100644 --- a/lib/model.js +++ b/lib/model.js @@ -10,6 +10,7 @@ const Document = require('./document'); const DocumentNotFoundError = require('./error/notFound'); const DivergentArrayError = require('./error/divergentArray'); const EventEmitter = require('events').EventEmitter; +const Kareem = require('kareem'); const MongooseBuffer = require('./types/buffer'); const MongooseError = require('./error/index'); const OverwriteModelError = require('./error/overwriteModel'); @@ -61,7 +62,7 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths'); const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField'); const setDottedPath = require('./helpers/path/setDottedPath'); -const STATES = require('./connectionstate'); +const STATES = require('./connectionState'); const util = require('util'); const utils = require('./utils'); const MongooseBulkWriteError = require('./error/bulkWriteError'); @@ -1004,18 +1005,18 @@ Model.prototype.$__where = function _where(where) { }; /** - * Removes this document from the db. Equivalent to `.remove()`. + * Delete this document from the db. * * #### Example: * - * product = await product.deleteOne(); + * await product.deleteOne(); * await Product.findById(product._id); // null * - * @return {Promise} Promise + * @return {Query} Query * @api public */ -Model.prototype.deleteOne = async function deleteOne(options) { +Model.prototype.deleteOne = function deleteOne(options) { if (typeof options === 'function' || typeof arguments[1] === 'function') { throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback'); @@ -1029,51 +1030,43 @@ Model.prototype.deleteOne = async function deleteOne(options) { this.$session(options.session); } - const res = await new Promise((resolve, reject) => { - this.$__deleteOne(options, (err, res) => { - if (err != null) { - return reject(err); - } - resolve(res); - }); - }); - - return res; -}; - -/*! - * ignore - */ - -Model.prototype.$__deleteOne = function $__deleteOne(options, cb) { - if (this.$__.isDeleted) { - return immediate(() => cb(null, this)); - } - + const self = this; const where = this.$__where(); - if (where instanceof MongooseError) { - return cb(where); + if (where instanceof Error) { + throw where; } + const query = self.constructor.deleteOne(where, options); - _applyCustomWhere(this, where); - - const session = this.$session(); - if (!options.hasOwnProperty('session')) { - options.session = session; + if (this.$session() != null) { + if (!('session' in query.options)) { + query.options.session = this.$session(); + } } - this[modelCollectionSymbol].deleteOne(where, options).then( - () => { - this.$__.isDeleted = true; - this.$emit('deleteOne', this); - this.constructor.emit('deleteOne', this); - return cb(null, this); - }, - err => { - this.$__.isDeleted = false; - cb(err); + query.pre(function queryPreDeleteOne(cb) { + self.constructor._middleware.execPre('deleteOne', self, [self], cb); + }); + query.pre(function callSubdocPreHooks(cb) { + each(self.$getAllSubdocs(), (subdoc, cb) => { + subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], cb); + }, cb); + }); + query.pre(function skipIfAlreadyDeleted(cb) { + if (self.$__.isDeleted) { + return cb(Kareem.skipWrappedFunction()); } - ); + return cb(); + }); + query.post(function callSubdocPostHooks(cb) { + each(self.$getAllSubdocs(), (subdoc, cb) => { + subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc], {}, cb); + }, cb); + }); + query.post(function queryPostDeleteOne(cb) { + self.constructor._middleware.execPost('deleteOne', self, [self], {}, cb); + }); + + return query; }; /** @@ -1468,6 +1461,17 @@ Model.createCollection = async function createCollection(options) { * // Will drop the 'age' index and create an index on `name` * await Customer.syncIndexes(); * + * You should be careful about running `syncIndexes()` on production applications under heavy load, + * because index builds are expensive operations, and unexpected index drops can lead to degraded + * performance. Before running `syncIndexes()`, you can use the [`diffIndexes()` function](#Model.diffIndexes()) + * to check what indexes `syncIndexes()` will drop and create. + * + * #### Example: + * + * const { toDrop, toCreate } = await Model.diffIndexes(); + * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop + * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create + * * @param {Object} [options] options to pass to `ensureIndexes()` * @param {Boolean} [options.background=null] if specified, overrides each index's `background` property * @return {Promise} @@ -1500,9 +1504,14 @@ Model.syncIndexes = async function syncIndexes(options) { /** * Does a dry-run of `Model.syncIndexes()`, returning the indexes that `syncIndexes()` would drop and create if you were to run `syncIndexes()`. * + * #### Example: + * + * const { toDrop, toCreate } = await Model.diffIndexes(); + * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop + * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create + * * @param {Object} [options] - * @returns {Promise} which contains an object, {toDrop, toCreate}, which - * are indexes that would be dropped in MongoDB and indexes that would be created in MongoDB. + * @return {Promise} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`. */ Model.diffIndexes = async function diffIndexes() { @@ -2257,34 +2266,6 @@ Model.countDocuments = function countDocuments(conditions, options) { return mq.countDocuments(conditions); }; -/** - * Counts number of documents that match `filter` in a database collection. - * - * This method is deprecated. If you want to count the number of documents in - * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount()) - * instead. Otherwise, use the [`countDocuments()`](https://mongoosejs.com/docs/api/model.html#Model.countDocuments()) function instead. - * - * #### Example: - * - * const count = await Adventure.count({ type: 'jungle' }); - * console.log('there are %d jungle adventures', count); - * - * @deprecated - * @param {Object} [filter] - * @return {Query} - * @api public - */ - -Model.count = function count(conditions) { - _checkContext(this, 'count'); - if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') { - throw new MongooseError('Model.count() no longer accepts a callback'); - } - - const mq = new this.Query({}, {}, this, this.$__collection); - - return mq.count(conditions); -}; /** * Creates a Query for a `distinct` operation. @@ -2417,7 +2398,7 @@ Model.$where = function $where() { * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. * @return {Query} * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html @@ -2533,7 +2514,7 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) { * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document * @param {Boolean} [options.new=false] if true, return the modified document rather than the original * @param {Object|String} [options.select] sets the document fields to return. @@ -2567,12 +2548,6 @@ Model.findByIdAndUpdate = function(id, update, options) { * * - `findOneAndDelete()` * - * This function differs slightly from `Model.findOneAndRemove()` in that - * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://www.mongodb.com/docs/manual/reference/method/db.collection.findAndModify/), - * as opposed to a `findOneAndDelete()` command. For most mongoose use cases, - * this distinction is purely pedantic. You should use `findOneAndDelete()` - * unless you have a good reason not to. - * * #### Example: * * A.findOneAndDelete(conditions, options) // return Query @@ -2594,7 +2569,7 @@ Model.findByIdAndUpdate = function(id, update, options) { * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. * @param {Object|String} [options.select] sets the document fields to return. * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0 @@ -2636,7 +2611,7 @@ Model.findOneAndDelete = function(conditions, options) { * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. * @return {Query} - * @see Model.findOneAndRemove https://mongoosejs.com/docs/api/model.html#Model.findOneAndRemove() + * @see Model.findOneAndDelete https://mongoosejs.com/docs/api/model.html#Model.findOneAndDelete() * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ */ @@ -2675,7 +2650,7 @@ Model.findByIdAndDelete = function(id, options) { * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {Object|String} [options.select] sets the document fields to return. * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0 * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. @@ -2702,104 +2677,6 @@ Model.findOneAndReplace = function(filter, replacement, options) { return mq.findOneAndReplace(filter, replacement, options); }; -/** - * Issue a mongodb findOneAndRemove command. - * - * Finds a matching document, removes it, and returns the found document (if any). - * - * This function triggers the following middleware. - * - * - `findOneAndRemove()` - * - * #### Example: - * - * A.findOneAndRemove(conditions, options) // return Query - * A.findOneAndRemove(conditions) // returns Query - * A.findOneAndRemove() // returns Query - * - * `findOneAndX` and `findByIdAndX` functions support limited validation. You can - * enable validation by setting the `runValidators` option. - * - * If you need full-fledged validation, use the traditional approach of first - * retrieving the document. - * - * const doc = await Model.findById(id); - * doc.name = 'jason bourne'; - * await doc.save(); - * - * @param {Object} conditions - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). - * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) - * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {Object|String} [options.select] sets the document fields to return. - * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0 - * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. - * @return {Query} - * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ - * @api public - */ - -Model.findOneAndRemove = function(conditions, options) { - _checkContext(this, 'findOneAndRemove'); - - if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') { - throw new MongooseError('Model.findOneAndRemove() no longer accepts a callback'); - } - - let fields; - if (options) { - fields = options.select; - options.select = undefined; - } - - const mq = new this.Query({}, {}, this, this.$__collection); - mq.select(fields); - - return mq.findOneAndRemove(conditions, options); -}; - -/** - * Issue a mongodb findOneAndRemove command by a document's _id field. `findByIdAndRemove(id, ...)` is equivalent to `findOneAndRemove({ _id: id }, ...)`. - * - * Finds a matching document, removes it, and returns the found document (if any). - * - * This function triggers the following middleware. - * - * - `findOneAndRemove()` - * - * #### Example: - * - * A.findByIdAndRemove(id, options) // return Query - * A.findByIdAndRemove(id) // returns Query - * A.findByIdAndRemove() // returns Query - * - * @param {Object|Number|String} id value of `_id` to query by - * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) - * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). - * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()) - * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update. - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {Object|String} [options.select] sets the document fields to return. - * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. - * @return {Query} - * @see Model.findOneAndRemove https://mongoosejs.com/docs/api/model.html#Model.findOneAndRemove() - * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ - */ - -Model.findByIdAndRemove = function(id, options) { - _checkContext(this, 'findByIdAndRemove'); - - if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') { - throw new MongooseError('Model.findByIdAndRemove() no longer accepts a callback'); - } - - return this.findOneAndRemove({ _id: id }, options); -}; - /** * Shortcut for saving one or more documents to the database. * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in @@ -2913,16 +2790,14 @@ Model.create = async function create(doc, options) { } } return res; - } else { - // ".bind(Promise)" is required, otherwise results in "TypeError: Promise.allSettled called on non-object" - const promiseType = !immediateError ? Promise.allSettled.bind(Promise) : Promise.all.bind(Promise); - let p = promiseType(args.map(async doc => { + } else if (!immediateError) { + res = await Promise.allSettled(args.map(async doc => { const Model = this.discriminators && doc[discriminatorKey] != null ? this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : this; if (Model == null) { throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + - `found for model "${this.modelName}"`); + `found for model "${this.modelName}"`); } let toSave = doc; @@ -2934,13 +2809,36 @@ Model.create = async function create(doc, options) { return toSave; })); + res = res.map(result => result.status === 'fulfilled' ? result.value : result.reason); + } else { + let firstError = null; + res = await Promise.all(args.map(async doc => { + const Model = this.discriminators && doc[discriminatorKey] != null ? + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : + this; + if (Model == null) { + throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + + `found for model "${this.modelName}"`); + } + try { + let toSave = doc; - // chain the mapper, only if "allSettled" is used - if (!immediateError) { - p = p.then(presult => presult.map(v => v.status === 'fulfilled' ? v.value : v.reason)); - } + if (!(toSave instanceof Model)) { + toSave = new Model(toSave); + } + + await toSave.$save(options); - res = await p; + return toSave; + } catch (err) { + if (!firstError) { + firstError = err; + } + } + })); + if (firstError) { + throw firstError; + } } @@ -3861,7 +3759,7 @@ Model.hydrate = function(obj, projection, options) { obj = applyProjection(obj, projection); } - const document = require('./queryhelpers').createModel(this, obj, projection); + const document = require('./queryHelpers').createModel(this, obj, projection); document.$init(obj, options); return document; }; @@ -4102,7 +4000,7 @@ Model.aggregate = function aggregate(pipeline, options) { * @param {Object} obj * @param {Object|Array|String} pathsOrOptions * @param {Object} [context] - * @return {Promise|undefined} + * @return {Promise} casted and validated copy of `obj` if validation succeeded * @api public */ @@ -4161,8 +4059,19 @@ Model.validate = async function validate(obj, pathsOrOptions, context) { pushNestedArrayPaths(paths, val, path); } - let remaining = paths.length; let error = null; + paths = new Set(paths); + + try { + obj = this.castObject(obj); + } catch (err) { + error = err; + for (const key of Object.keys(error.errors || {})) { + paths.delete(key); + } + } + + let remaining = paths.size; return new Promise((resolve, reject) => { for (const path of paths) { @@ -4178,20 +4087,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) { cur = cur[pieces[i]]; } - let val = get(obj, path, void 0); - - if (val != null) { - try { - val = schemaType.cast(val); - cur[pieces[pieces.length - 1]] = val; - } catch (err) { - error = error || new ValidationError(); - error.addError(path, err); - - _checkDone(); - continue; - } - } + const val = get(obj, path, void 0); schemaType.doValidate(val, err => { if (err) { @@ -4207,7 +4103,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) { if (error) { reject(error); } else { - resolve(); + resolve(obj); } } } diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 69fa6ad284c..a8a6c044240 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,6 +1,5 @@ 'use strict'; -exports.removeSubdocs = require('./removeSubdocs'); exports.saveSubdocs = require('./saveSubdocs'); exports.sharding = require('./sharding'); exports.trackTransaction = require('./trackTransaction'); diff --git a/lib/plugins/removeSubdocs.js b/lib/plugins/removeSubdocs.js deleted file mode 100644 index 85c7de0ff20..00000000000 --- a/lib/plugins/removeSubdocs.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const each = require('../helpers/each'); - -/*! - * ignore - */ - -module.exports = function removeSubdocs(schema) { - const unshift = true; - schema.s.hooks.pre('deleteOne', { document: true, query: false }, function removeSubDocsPreRemove(next) { - if (this.$isSubdocument) { - next(); - return; - } - if (this.$__ == null) { - next(); - return; - } - - const _this = this; - const subdocs = this.$getAllSubdocs(); - - each(subdocs, function(subdoc, cb) { - subdoc.$__deleteOne(cb); - }, function(error) { - if (error) { - return _this.$__schema.s.hooks.execPost('deleteOne:error', _this, [_this], { error: error }, function(error) { - next(error); - }); - } - next(); - }); - }, null, unshift); -}; diff --git a/lib/query.js b/lib/query.js index 8afac05cae1..0e33882933d 100644 --- a/lib/query.js +++ b/lib/query.js @@ -22,7 +22,7 @@ const clone = require('./helpers/clone'); const completeMany = require('./helpers/query/completeMany'); const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); const hasDollarKeys = require('./helpers/query/hasDollarKeys'); -const helpers = require('./queryhelpers'); +const helpers = require('./queryHelpers'); const immediate = require('./helpers/immediate'); const internalToObjectOptions = require('./options').internalToObjectOptions; const isExclusive = require('./helpers/projection/isExclusive'); @@ -37,6 +37,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'); @@ -1218,7 +1219,6 @@ Query.prototype.toString = function toString() { this.op === 'deleteMany' || this.op === 'deleteOne' || this.op === 'findOneAndDelete' || - this.op === 'findOneAndRemove' || this.op === 'remove') { return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)})`; } @@ -1549,13 +1549,13 @@ Query.prototype.getOptions = function() { * * - [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) * - * The following options are for `find()`, `findOne()`, `findOneAndUpdate()`, `findOneAndRemove()`, `findOneAndDelete()`, `updateOne()`, and `deleteOne()`: + * The following options are for `find()`, `findOne()`, `findOneAndUpdate()`, `findOneAndDelete()`, `updateOne()`, and `deleteOne()`: * * - [sort](https://www.mongodb.com/docs/manual/reference/method/cursor.sort/) * - * The following options are for `findOneAndUpdate()` and `findOneAndRemove()` + * The following options are for `findOneAndUpdate()` and `findOneAndDelete()` * - * - rawResult + * - includeResultMetadata * * The following options are for all operations: * @@ -1636,10 +1636,6 @@ Query.prototype.setOptions = function(options, overwrite) { delete options.translateAliases; } - if ('rawResult' in options) { - printRawResultDeprecationWarning(); - } - if (options.lean == null && this.schema && 'lean' in this.schema.options) { this._mongooseOptions.lean = this.schema.options.lean; } @@ -1674,15 +1670,6 @@ Query.prototype.setOptions = function(options, overwrite) { return this; }; -/*! - * ignore - */ - -const printRawResultDeprecationWarning = util.deprecate( - function printRawResultDeprecationWarning() {}, - 'The `rawResult` option for Mongoose queries is deprecated. Use `includeResultMetadata: false` as a replacement for `rawResult: true`.' -); - /*! * ignore */ @@ -2349,8 +2336,6 @@ Query.prototype.find = function(conditions) { this.error(new ObjectParameterError(conditions, 'filter', 'find')); } - Query.base.find.call(this); - return this; }; @@ -2456,7 +2441,7 @@ Query.prototype.collation = function(value) { */ Query.prototype._completeOne = function(doc, res, callback) { - if (!doc && !this.options.rawResult && !this.options.includeResultMetadata) { + if (!doc && !this.options.includeResultMetadata) { return callback(null, null); } @@ -2465,7 +2450,7 @@ Query.prototype._completeOne = function(doc, res, callback) { const userProvidedFields = this._userProvidedFields || {}; // `populate`, `lean` const mongooseOptions = this._mongooseOptions; - // `rawResult` + const options = this.options; if (!options.lean && mongooseOptions.lean) { options.lean = mongooseOptions.lean; @@ -2598,40 +2583,9 @@ Query.prototype.findOne = function(conditions, projection, options) { this.error(new ObjectParameterError(conditions, 'filter', 'findOne')); } - Query.base.findOne.call(this); - return this; }; -/** - * Execute a count query - * - * @see count https://www.mongodb.com/docs/manual/reference/method/db.collection.count/ - * @api private - */ - -Query.prototype._count = async function _count() { - try { - this.cast(this.model); - } catch (err) { - this.error(err); - } - - if (this.error()) { - throw this.error(); - } - - applyGlobalMaxTimeMS(this.options, this.model); - applyGlobalDiskUse(this.options, this.model); - - const options = this._optionsForExec(); - - this._applyTranslateAliases(options); - - const conds = this._conditions; - - return this._collection.collection.count(conds, options); -}; /** * Execute a countDocuments query @@ -2708,50 +2662,6 @@ Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount return this._collection.collection.estimatedDocumentCount(options); }; -/** - * Specifies this query as a `count` query. - * - * This method is deprecated. If you want to count the number of documents in - * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/query.html#Query.prototype.estimatedDocumentCount()) - * instead. Otherwise, use the [`countDocuments()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.countDocuments()) function instead. - * - * This function triggers the following middleware. - * - * - `count()` - * - * #### Example: - * - * const countQuery = model.where({ 'color': 'black' }).count(); - * - * query.count({ color: 'black' }).count().exec(); - * - * await query.count({ color: 'black' }); - * - * query.where('color', 'black').count(); - * - * @deprecated - * @param {Object} [filter] count documents that match this object - * @return {Query} this - * @see count https://www.mongodb.com/docs/manual/reference/method/db.collection.count/ - * @api public - */ - -Query.prototype.count = function(filter) { - if (typeof filter === 'function' || - typeof arguments[1] === 'function') { - throw new MongooseError('Query.prototype.count() no longer accepts a callback'); - } - - this.op = 'count'; - this._validateOp(); - - if (mquery.canMerge(filter)) { - this.merge(filter); - } - - return this; -}; - /** * Specifies this query as a `estimatedDocumentCount()` query. Faster than * using `countDocuments()` for large collections because @@ -2952,9 +2862,72 @@ Query.prototype.sort = function(arg) { throw new Error('sort() only takes 1 Argument'); } - return Query.base.sort.call(this, arg); + if (this.options.sort == null) { + 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.'); + } + + return this; }; +/*! + * 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 * remove, except it deletes at most one document regardless of the `single` @@ -3003,8 +2976,6 @@ Query.prototype.deleteOne = function deleteOne(filter, options) { this.error(new ObjectParameterError(filter, 'filter', 'deleteOne')); } - Query.base.deleteOne.call(this); - return this; }; @@ -3078,8 +3049,6 @@ Query.prototype.deleteMany = function(filter, options) { this.error(new ObjectParameterError(filter, 'filter', 'deleteMany')); } - Query.base.deleteMany.call(this); - return this; }; @@ -3120,7 +3089,7 @@ Query.prototype._deleteMany = async function _deleteMany() { */ function completeOne(model, doc, res, options, fields, userProvidedFields, pop, callback) { - if ((options.rawResult || options.includeResultMetadata) && doc == null) { + if (options.includeResultMetadata && doc == null) { _init(null); return null; } @@ -3133,7 +3102,7 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop, } - if (options.rawResult || options.includeResultMetadata) { + if (options.includeResultMetadata) { if (doc && casted) { if (options.session != null) { casted.$session(options.session); @@ -3188,7 +3157,6 @@ function prepareDiscriminatorCriteria(query) { * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 * - `runValidators`: if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. * - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. - * - `rawResult`: if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * * #### Example: * @@ -3203,7 +3171,7 @@ function prepareDiscriminatorCriteria(query) { * @param {Object|Query} [filter] * @param {Object} [doc] * @param {Object} [options] - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. @@ -3312,10 +3280,6 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { applyGlobalMaxTimeMS(this.options, this.model); applyGlobalDiskUse(this.options, this.model); - if (this.options.rawResult && this.options.includeResultMetadata === false) { - throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false'); - } - if ('strict' in this.options) { this._mongooseOptions.strict = this.options.strict; } @@ -3366,7 +3330,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { for (const fn of this._transforms) { res = fn(res); } - const doc = options.includeResultMetadata === false ? res : res.value; + const doc = !options.includeResultMetadata ? res : res.value; return new Promise((resolve, reject) => { this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => { @@ -3378,60 +3342,6 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { }); }; -/** - * Legacy alias for `findOneAndDelete()`. - * - * Finds a matching document, removes it, returns the found document (if any). - * - * This function triggers the following middleware. - * - * - `findOneAndRemove()` - * - * #### Available options - * - * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update - * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 - * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * - * #### Example: - * - * A.where().findOneAndRemove(conditions, options) // return Query - * A.where().findOneAndRemove(conditions) // returns Query - * A.where().findOneAndRemove() // returns Query - * - * @method findOneAndRemove - * @memberOf Query - * @instance - * @param {Object} [conditions] - * @param {Object} [options] - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) - * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). - * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) - * @return {Query} this - * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/ - * @api public - */ - -Query.prototype.findOneAndRemove = function(conditions, options) { - if (typeof conditions === 'function' || - typeof options === 'function' || - typeof arguments[2] === 'function') { - throw new MongooseError('Query.prototype.findOneAndRemove() no longer accepts a callback'); - } - - this.op = 'findOneAndRemove'; - this._validateOp(); - this._validate(); - - if (mquery.canMerge(conditions)) { - this.merge(conditions); - } - - options && this.setOptions(options); - - return this; -}; - /** * Issues a MongoDB [findOneAndDelete](https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndDelete/) command. * @@ -3445,7 +3355,6 @@ Query.prototype.findOneAndRemove = function(conditions, options) { * * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 - * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * * #### Callback Signature * @@ -3464,7 +3373,7 @@ Query.prototype.findOneAndRemove = function(conditions, options) { * @memberOf Query * @param {Object} [filter] * @param {Object} [options] - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @return {Query} this @@ -3508,9 +3417,6 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { } const includeResultMetadata = this.options.includeResultMetadata; - if (this.options.rawResult && includeResultMetadata === false) { - throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false'); - } const filter = this._conditions; const options = this._optionsForExec(this.model); @@ -3520,7 +3426,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { for (const fn of this._transforms) { res = fn(res); } - const doc = includeResultMetadata === false ? res : res.value; + const doc = !includeResultMetadata ? res : res.value; return new Promise((resolve, reject) => { this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => { @@ -3545,7 +3451,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { * * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 - * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * - `includeResultMetadata`: if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * * #### Callback Signature * @@ -3565,7 +3471,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() { * @param {Object} [filter] * @param {Object} [replacement] * @param {Object} [options] - * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) + * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. @@ -3645,9 +3551,6 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() { convertNewToReturnDocument(options); const includeResultMetadata = this.options.includeResultMetadata; - if (this.options.rawResult && includeResultMetadata === false) { - throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false'); - } const modelOpts = { skipId: true }; if ('strict' in this._mongooseOptions) { @@ -3679,7 +3582,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() { res = fn(res); } - const doc = includeResultMetadata === false ? res : res.value; + const doc = !includeResultMetadata ? res : res.value; return new Promise((resolve, reject) => { this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => { if (err) { @@ -3711,19 +3614,6 @@ function convertNewToReturnDocument(options) { } } -/** - * Execute a `findOneAndRemove`. Alias for `findOneAndDelete` - * - * @return {Query} this - * @method _findOneAndRemove - * @memberOf Query - * @instance - * @api private - */ -Query.prototype._findOneAndRemove = async function _findOneAndRemove() { - return this._findOneAndDelete(); -}; - /** * Get options from query opts, falling back to the base mongoose object. * @param {Query} query @@ -3774,7 +3664,7 @@ function _completeOneLean(schema, doc, path, res, opts, callback) { return; } } - if (opts.rawResult) { + if (opts.includeResultMetadata) { return callback(null, res); } return callback(null, doc); @@ -4224,7 +4114,7 @@ function _update(query, op, filter, doc, options, callback) { return query; } - return Query.base[op].call(query, filter, void 0, options, callback); + return query; } /** @@ -4306,14 +4196,12 @@ Query.prototype.orFail = function(err) { } break; case 'findOneAndDelete': - case 'findOneAndRemove': - if ((res && res.lastErrorObject && res.lastErrorObject.n) === 0) { - throw _orFailError(err, this); - } - break; case 'findOneAndUpdate': case 'findOneAndReplace': - if ((res && res.lastErrorObject && res.lastErrorObject.updatedExisting) === false) { + if (this.options.includeResultMetadata && res != null && res.value == null) { + throw _orFailError(err, this); + } + if (!this.options.includeResultMetadata && res == null) { throw _orFailError(err, this); } break; @@ -4420,14 +4308,23 @@ Query.prototype.exec = async function exec(op) { this._executionStack = new Error(); } - await _executePreExecHooks(this); + let skipWrappedFunction = null; + try { + await _executePreExecHooks(this); + } catch (err) { + if (err instanceof Kareem.skipWrappedFunction) { + skipWrappedFunction = err; + } else { + throw err; + } + } let res; let error = null; try { await _executePreHooks(this); - res = await this[thunk](); + res = skipWrappedFunction ? skipWrappedFunction.args[0] : await this[thunk](); for (const fn of this._transforms) { res = fn(res); @@ -5103,7 +5000,9 @@ Query.prototype.tailable = function(val, opts) { } } - return Query.base.tailable.call(this, val); + this.options.tailable = arguments.length ? !!val : true; + + return this; }; /** diff --git a/lib/queryhelpers.js b/lib/queryHelpers.js similarity index 100% rename from lib/queryhelpers.js rename to lib/queryHelpers.js diff --git a/lib/schema.js b/lib/schema.js index 261211c1fb6..e0e0b03bf1d 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -7,10 +7,10 @@ const EventEmitter = require('events').EventEmitter; const Kareem = require('kareem'); const MongooseError = require('./error/mongooseError'); -const SchemaType = require('./schematype'); +const SchemaType = require('./schemaType'); const SchemaTypeOptions = require('./options/SchemaTypeOptions'); const VirtualOptions = require('./options/VirtualOptions'); -const VirtualType = require('./virtualtype'); +const VirtualType = require('./virtualType'); const addAutoId = require('./helpers/schema/addAutoId'); const clone = require('./helpers/clone'); const get = require('./helpers/get'); diff --git a/lib/schema/DocumentArrayElement.js b/lib/schema/DocumentArrayElement.js index a2fb9a44a23..5250b74b505 100644 --- a/lib/schema/DocumentArrayElement.js +++ b/lib/schema/DocumentArrayElement.js @@ -5,8 +5,8 @@ 'use strict'; const MongooseError = require('../error/mongooseError'); -const SchemaType = require('../schematype'); -const SubdocumentPath = require('./SubdocumentPath'); +const SchemaType = require('../schemaType'); +const SchemaSubdocument = require('./subdocument'); const getConstructor = require('../helpers/discriminator/getConstructor'); /** @@ -18,7 +18,7 @@ const getConstructor = require('../helpers/discriminator/getConstructor'); * @api public */ -function DocumentArrayElement(path, options) { +function SchemaDocumentArrayElement(path, options) { this.$parentSchemaType = options && options.$parentSchemaType; if (!this.$parentSchemaType) { throw new MongooseError('Cannot create DocumentArrayElement schematype without a parent'); @@ -36,15 +36,15 @@ function DocumentArrayElement(path, options) { * * @api public */ -DocumentArrayElement.schemaName = 'DocumentArrayElement'; +SchemaDocumentArrayElement.schemaName = 'DocumentArrayElement'; -DocumentArrayElement.defaultOptions = {}; +SchemaDocumentArrayElement.defaultOptions = {}; /*! * Inherits from SchemaType. */ -DocumentArrayElement.prototype = Object.create(SchemaType.prototype); -DocumentArrayElement.prototype.constructor = DocumentArrayElement; +SchemaDocumentArrayElement.prototype = Object.create(SchemaType.prototype); +SchemaDocumentArrayElement.prototype.constructor = SchemaDocumentArrayElement; /** * Casts `val` for DocumentArrayElement. @@ -53,7 +53,7 @@ DocumentArrayElement.prototype.constructor = DocumentArrayElement; * @api private */ -DocumentArrayElement.prototype.cast = function(...args) { +SchemaDocumentArrayElement.prototype.cast = function(...args) { return this.$parentSchemaType.cast(...args)[0]; }; @@ -65,14 +65,14 @@ DocumentArrayElement.prototype.cast = function(...args) { * @api private */ -DocumentArrayElement.prototype.doValidate = function(value, fn, scope, options) { +SchemaDocumentArrayElement.prototype.doValidate = function(value, fn, scope, options) { const Constructor = getConstructor(this.caster, value); if (value && !(value instanceof Constructor)) { value = new Constructor(value, scope, null, null, options && options.index != null ? options.index : null); } - return SubdocumentPath.prototype.doValidate.call(this, value, fn, scope, options); + return SchemaSubdocument.prototype.doValidate.call(this, value, fn, scope, options); }; /** @@ -82,7 +82,7 @@ DocumentArrayElement.prototype.doValidate = function(value, fn, scope, options) * @api private */ -DocumentArrayElement.prototype.clone = function() { +SchemaDocumentArrayElement.prototype.clone = function() { this.options.$parentSchemaType = this.$parentSchemaType; const ret = SchemaType.prototype.clone.apply(this, arguments); delete this.options.$parentSchemaType; @@ -97,4 +97,4 @@ DocumentArrayElement.prototype.clone = function() { * Module exports. */ -module.exports = DocumentArrayElement; +module.exports = SchemaDocumentArrayElement; diff --git a/lib/schema/array.js b/lib/schema/array.js index 6d1c6de9628..f352d648589 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -8,7 +8,7 @@ const $exists = require('./operators/exists'); const $type = require('./operators/type'); const MongooseError = require('../error/mongooseError'); const SchemaArrayOptions = require('../options/SchemaArrayOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const CastError = SchemaType.CastError; const Mixed = require('./mixed'); const arrayDepth = require('../helpers/arrayDepth'); diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 4c7dcb77039..c0c324fb370 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -5,7 +5,7 @@ */ const CastError = require('../error/cast'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const castBigInt = require('../cast/bigint'); const utils = require('../utils'); diff --git a/lib/schema/boolean.js b/lib/schema/boolean.js index 316c825df45..d8da46a4c4f 100644 --- a/lib/schema/boolean.js +++ b/lib/schema/boolean.js @@ -5,7 +5,7 @@ */ const CastError = require('../error/cast'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const castBoolean = require('../cast/boolean'); const utils = require('../utils'); diff --git a/lib/schema/buffer.js b/lib/schema/buffer.js index 5bfaabcd2f6..9e4586ffa3a 100644 --- a/lib/schema/buffer.js +++ b/lib/schema/buffer.js @@ -6,7 +6,7 @@ const MongooseBuffer = require('../types/buffer'); const SchemaBufferOptions = require('../options/SchemaBufferOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const handleBitwiseOperator = require('./operators/bitwise'); const utils = require('../utils'); diff --git a/lib/schema/date.js b/lib/schema/date.js index 61928bb457d..1d2f871e263 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -6,7 +6,7 @@ const MongooseError = require('../error/index'); const SchemaDateOptions = require('../options/SchemaDateOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const castDate = require('../cast/date'); const getConstructorName = require('../helpers/getConstructorName'); const utils = require('../utils'); diff --git a/lib/schema/decimal128.js b/lib/schema/decimal128.js index 650d78c2571..356bf52c3ba 100644 --- a/lib/schema/decimal128.js +++ b/lib/schema/decimal128.js @@ -4,7 +4,7 @@ 'use strict'; -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const CastError = SchemaType.CastError; const castDecimal128 = require('../cast/decimal128'); const utils = require('../utils'); @@ -19,7 +19,7 @@ const isBsonType = require('../helpers/isBsonType'); * @api public */ -function Decimal128(key, options) { +function SchemaDecimal128(key, options) { SchemaType.call(this, key, options, 'Decimal128'); } @@ -29,21 +29,21 @@ function Decimal128(key, options) { * * @api public */ -Decimal128.schemaName = 'Decimal128'; +SchemaDecimal128.schemaName = 'Decimal128'; -Decimal128.defaultOptions = {}; +SchemaDecimal128.defaultOptions = {}; /*! * Inherits from SchemaType. */ -Decimal128.prototype = Object.create(SchemaType.prototype); -Decimal128.prototype.constructor = Decimal128; +SchemaDecimal128.prototype = Object.create(SchemaType.prototype); +SchemaDecimal128.prototype.constructor = SchemaDecimal128; /*! * ignore */ -Decimal128._cast = castDecimal128; +SchemaDecimal128._cast = castDecimal128; /** * Sets a default option for all Decimal128 instances. @@ -64,9 +64,9 @@ Decimal128._cast = castDecimal128; * @api public */ -Decimal128.set = SchemaType.set; +SchemaDecimal128.set = SchemaType.set; -Decimal128.setters = []; +SchemaDecimal128.setters = []; /** * Attaches a getter for all Decimal128 instances @@ -83,7 +83,7 @@ Decimal128.setters = []; * @api public */ -Decimal128.get = SchemaType.get; +SchemaDecimal128.get = SchemaType.get; /** * Get/set the function used to cast arbitrary values to decimals. @@ -107,7 +107,7 @@ Decimal128.get = SchemaType.get; * @api public */ -Decimal128.cast = function cast(caster) { +SchemaDecimal128.cast = function cast(caster) { if (arguments.length === 0) { return this._cast; } @@ -123,7 +123,7 @@ Decimal128.cast = function cast(caster) { * ignore */ -Decimal128._defaultCaster = v => { +SchemaDecimal128._defaultCaster = v => { if (v != null && !isBsonType(v, 'Decimal128')) { throw new Error(); } @@ -134,7 +134,7 @@ Decimal128._defaultCaster = v => { * ignore */ -Decimal128._checkRequired = v => isBsonType(v, 'Decimal128'); +SchemaDecimal128._checkRequired = v => isBsonType(v, 'Decimal128'); /** * Override the function the required validator uses to check whether a string @@ -147,7 +147,7 @@ Decimal128._checkRequired = v => isBsonType(v, 'Decimal128'); * @api public */ -Decimal128.checkRequired = SchemaType.checkRequired; +SchemaDecimal128.checkRequired = SchemaType.checkRequired; /** * Check if the given value satisfies a required validator. @@ -158,7 +158,7 @@ Decimal128.checkRequired = SchemaType.checkRequired; * @api public */ -Decimal128.prototype.checkRequired = function checkRequired(value, doc) { +SchemaDecimal128.prototype.checkRequired = function checkRequired(value, doc) { if (SchemaType._isRef(this, value, doc, true)) { return !!value; } @@ -167,7 +167,7 @@ Decimal128.prototype.checkRequired = function checkRequired(value, doc) { // plugins like mongoose-float use `inherits()` for pre-ES6. const _checkRequired = typeof this.constructor.checkRequired === 'function' ? this.constructor.checkRequired() : - Decimal128.checkRequired(); + SchemaDecimal128.checkRequired(); return _checkRequired(value); }; @@ -181,7 +181,7 @@ Decimal128.prototype.checkRequired = function checkRequired(value, doc) { * @api private */ -Decimal128.prototype.cast = function(value, doc, init) { +SchemaDecimal128.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { if (isBsonType(value, 'Decimal128')) { return value; @@ -196,7 +196,7 @@ Decimal128.prototype.cast = function(value, doc, init) { } else if (typeof this.constructor.cast === 'function') { castDecimal128 = this.constructor.cast(); } else { - castDecimal128 = Decimal128.cast(); + castDecimal128 = SchemaDecimal128.cast(); } try { @@ -214,7 +214,7 @@ function handleSingle(val) { return this.cast(val); } -Decimal128.prototype.$conditionalHandlers = +SchemaDecimal128.prototype.$conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, { $gt: handleSingle, $gte: handleSingle, @@ -226,4 +226,4 @@ Decimal128.prototype.$conditionalHandlers = * Module exports. */ -module.exports = Decimal128; +module.exports = SchemaDecimal128; diff --git a/lib/schema/documentarray.js b/lib/schema/documentArray.js similarity index 91% rename from lib/schema/documentarray.js rename to lib/schema/documentArray.js index 7f8a39a04d6..443e3caa13c 100644 --- a/lib/schema/documentarray.js +++ b/lib/schema/documentArray.js @@ -4,13 +4,13 @@ * Module dependencies. */ -const ArrayType = require('./array'); const CastError = require('../error/cast'); const DocumentArrayElement = require('./DocumentArrayElement'); const EventEmitter = require('events').EventEmitter; +const SchemaArray = require('./array'); const SchemaDocumentArrayOptions = require('../options/SchemaDocumentArrayOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const discriminator = require('../helpers/model/discriminator'); const handleIdOption = require('../helpers/schema/handleIdOption'); const handleSpreadDoc = require('../helpers/document/handleSpreadDoc'); @@ -36,12 +36,12 @@ let Subdocument; * @api public */ -function DocumentArrayPath(key, schema, options, schemaOptions) { +function SchemaDocumentArray(key, schema, options, schemaOptions) { if (schema.options && schema.options.timeseries) { throw new InvalidSchemaOptionError(key, 'timeseries'); } - const schemaTypeIdOption = DocumentArrayPath.defaultOptions && - DocumentArrayPath.defaultOptions._id; + const schemaTypeIdOption = SchemaDocumentArray.defaultOptions && + SchemaDocumentArray.defaultOptions._id; if (schemaTypeIdOption != null) { schemaOptions = schemaOptions || {}; schemaOptions._id = schemaTypeIdOption; @@ -56,7 +56,7 @@ function DocumentArrayPath(key, schema, options, schemaOptions) { const EmbeddedDocument = _createConstructor(schema, options); EmbeddedDocument.prototype.$basePath = key; - ArrayType.call(this, key, EmbeddedDocument, options); + SchemaArray.call(this, key, EmbeddedDocument, options); this.schema = schema; this.schemaOptions = schemaOptions || {}; @@ -96,7 +96,7 @@ function DocumentArrayPath(key, schema, options, schemaOptions) { * * @api public */ -DocumentArrayPath.schemaName = 'DocumentArray'; +SchemaDocumentArray.schemaName = 'DocumentArray'; /** * Options for all document arrays. @@ -106,14 +106,14 @@ DocumentArrayPath.schemaName = 'DocumentArray'; * @api public */ -DocumentArrayPath.options = { castNonArrays: true }; +SchemaDocumentArray.options = { castNonArrays: true }; /*! - * Inherits from ArrayType. + * Inherits from SchemaArray. */ -DocumentArrayPath.prototype = Object.create(ArrayType.prototype); -DocumentArrayPath.prototype.constructor = DocumentArrayPath; -DocumentArrayPath.prototype.OptionsConstructor = SchemaDocumentArrayOptions; +SchemaDocumentArray.prototype = Object.create(SchemaArray.prototype); +SchemaDocumentArray.prototype.constructor = SchemaDocumentArray; +SchemaDocumentArray.prototype.OptionsConstructor = SchemaDocumentArrayOptions; /*! * ignore @@ -182,7 +182,7 @@ function _createConstructor(schema, options, baseClass) { * @api public */ -DocumentArrayPath.prototype.discriminator = function(name, schema, options) { +SchemaDocumentArray.prototype.discriminator = function(name, schema, options) { if (typeof name === 'function') { name = utils.getFunctionName(name); } @@ -219,7 +219,7 @@ DocumentArrayPath.prototype.discriminator = function(name, schema, options) { * @api private */ -DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) { +SchemaDocumentArray.prototype.doValidate = function(array, fn, scope, options) { // lazy load MongooseDocumentArray || (MongooseDocumentArray = require('../types/DocumentArray')); @@ -295,7 +295,7 @@ DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) { * @api private */ -DocumentArrayPath.prototype.doValidateSync = function(array, scope, options) { +SchemaDocumentArray.prototype.doValidateSync = function(array, scope, options) { const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope); if (schemaTypeError != null) { return schemaTypeError; @@ -344,7 +344,7 @@ DocumentArrayPath.prototype.doValidateSync = function(array, scope, options) { * ignore */ -DocumentArrayPath.prototype.getDefault = function(scope, init, options) { +SchemaDocumentArray.prototype.getDefault = function(scope, init, options) { let ret = typeof this.defaultValue === 'function' ? this.defaultValue.call(scope) : this.defaultValue; @@ -395,7 +395,7 @@ const initDocumentOptions = Object.freeze({ skipId: false, willInit: true }); * @api private */ -DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) { +SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) { // lazy load MongooseDocumentArray || (MongooseDocumentArray = require('../types/DocumentArray')); @@ -412,7 +412,7 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) { const path = options.path || this.path; if (!Array.isArray(value)) { - if (!init && !DocumentArrayPath.options.castNonArrays) { + if (!init && !SchemaDocumentArray.options.castNonArrays) { throw new CastError('DocumentArray', value, this.path, null, this); } // gh-2442 mark whole array as modified if we're initializing a doc from @@ -520,7 +520,7 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) { * ignore */ -DocumentArrayPath.prototype.clone = function() { +SchemaDocumentArray.prototype.clone = function() { const options = Object.assign({}, this.options); const schematype = new this.constructor(this.path, this.schema, options, this.schemaOptions); schematype.validators = this.validators.slice(); @@ -536,7 +536,7 @@ DocumentArrayPath.prototype.clone = function() { * ignore */ -DocumentArrayPath.prototype.applyGetters = function(value, scope) { +SchemaDocumentArray.prototype.applyGetters = function(value, scope) { return SchemaType.prototype.applyGetters.call(this, value, scope); }; @@ -585,7 +585,7 @@ function scopePaths(array, fields, init) { * ignore */ -DocumentArrayPath.defaultOptions = {}; +SchemaDocumentArray.defaultOptions = {}; /** * Sets a default option for all DocumentArray instances. @@ -603,9 +603,9 @@ DocumentArrayPath.defaultOptions = {}; * @api public */ -DocumentArrayPath.set = SchemaType.set; +SchemaDocumentArray.set = SchemaType.set; -DocumentArrayPath.setters = []; +SchemaDocumentArray.setters = []; /** * Attaches a getter for all DocumentArrayPath instances @@ -617,10 +617,10 @@ DocumentArrayPath.setters = []; * @api public */ -DocumentArrayPath.get = SchemaType.get; +SchemaDocumentArray.get = SchemaType.get; /*! * Module exports. */ -module.exports = DocumentArrayPath; +module.exports = SchemaDocumentArray; diff --git a/lib/schema/index.js b/lib/schema/index.js index 4bd4d8d5934..0caf091adf2 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -6,18 +6,18 @@ 'use strict'; exports.Array = require('./array'); -exports.Boolean = require('./boolean'); exports.BigInt = require('./bigint'); +exports.Boolean = require('./boolean'); exports.Buffer = require('./buffer'); exports.Date = require('./date'); exports.Decimal128 = exports.Decimal = require('./decimal128'); -exports.DocumentArray = require('./documentarray'); +exports.DocumentArray = require('./documentArray'); exports.Map = require('./map'); exports.Mixed = require('./mixed'); exports.Number = require('./number'); -exports.ObjectId = require('./objectid'); +exports.ObjectId = require('./objectId'); exports.String = require('./string'); -exports.Subdocument = require('./SubdocumentPath'); +exports.Subdocument = require('./subdocument'); exports.UUID = require('./uuid'); // alias diff --git a/lib/schema/map.js b/lib/schema/map.js index ce25a11f568..89ecc6a1cd0 100644 --- a/lib/schema/map.js +++ b/lib/schema/map.js @@ -6,12 +6,12 @@ const MongooseMap = require('../types/map'); const SchemaMapOptions = require('../options/SchemaMapOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); /*! * ignore */ -class Map extends SchemaType { +class SchemaMap extends SchemaType { constructor(key, options) { super(key, options, 'Map'); this.$isSchemaMap = true; @@ -75,10 +75,10 @@ class Map extends SchemaType { * * @api public */ -Map.schemaName = 'Map'; +SchemaMap.schemaName = 'Map'; -Map.prototype.OptionsConstructor = SchemaMapOptions; +SchemaMap.prototype.OptionsConstructor = SchemaMapOptions; -Map.defaultOptions = {}; +SchemaMap.defaultOptions = {}; -module.exports = Map; +module.exports = SchemaMap; diff --git a/lib/schema/mixed.js b/lib/schema/mixed.js index bd38a286213..a645a981f7b 100644 --- a/lib/schema/mixed.js +++ b/lib/schema/mixed.js @@ -4,7 +4,7 @@ 'use strict'; -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const symbols = require('./symbols'); const isObject = require('../helpers/isObject'); const utils = require('../utils'); @@ -18,7 +18,7 @@ const utils = require('../utils'); * @api public */ -function Mixed(path, options) { +function SchemaMixed(path, options) { if (options && options.default) { const def = options.default; if (Array.isArray(def) && def.length === 0) { @@ -43,15 +43,15 @@ function Mixed(path, options) { * * @api public */ -Mixed.schemaName = 'Mixed'; +SchemaMixed.schemaName = 'Mixed'; -Mixed.defaultOptions = {}; +SchemaMixed.defaultOptions = {}; /*! * Inherits from SchemaType. */ -Mixed.prototype = Object.create(SchemaType.prototype); -Mixed.prototype.constructor = Mixed; +SchemaMixed.prototype = Object.create(SchemaType.prototype); +SchemaMixed.prototype.constructor = SchemaMixed; /** * Attaches a getter for all Mixed paths. @@ -71,7 +71,7 @@ Mixed.prototype.constructor = Mixed; * @api public */ -Mixed.get = SchemaType.get; +SchemaMixed.get = SchemaType.get; /** * Sets a default option for all Mixed instances. @@ -92,9 +92,9 @@ Mixed.get = SchemaType.get; * @api public */ -Mixed.set = SchemaType.set; +SchemaMixed.set = SchemaType.set; -Mixed.setters = []; +SchemaMixed.setters = []; /** * Casts `val` for Mixed. @@ -105,7 +105,7 @@ Mixed.setters = []; * @api private */ -Mixed.prototype.cast = function(val) { +SchemaMixed.prototype.cast = function(val) { if (val instanceof Error) { return utils.errorToPOJO(val); } @@ -120,7 +120,7 @@ Mixed.prototype.cast = function(val) { * @api private */ -Mixed.prototype.castForQuery = function($cond, val) { +SchemaMixed.prototype.castForQuery = function($cond, val) { return val; }; @@ -128,4 +128,4 @@ Mixed.prototype.castForQuery = function($cond, val) { * Module exports. */ -module.exports = Mixed; +module.exports = SchemaMixed; diff --git a/lib/schema/number.js b/lib/schema/number.js index b2695cd94a6..8400cfb1bf6 100644 --- a/lib/schema/number.js +++ b/lib/schema/number.js @@ -6,7 +6,7 @@ const MongooseError = require('../error/index'); const SchemaNumberOptions = require('../options/SchemaNumberOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const castNumber = require('../cast/number'); const handleBitwiseOperator = require('./operators/bitwise'); const utils = require('../utils'); diff --git a/lib/schema/objectid.js b/lib/schema/objectId.js similarity index 84% rename from lib/schema/objectid.js rename to lib/schema/objectId.js index f0a0b6be747..d1d502bfa03 100644 --- a/lib/schema/objectid.js +++ b/lib/schema/objectId.js @@ -5,7 +5,7 @@ 'use strict'; const SchemaObjectIdOptions = require('../options/SchemaObjectIdOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const castObjectId = require('../cast/objectid'); const getConstructorName = require('../helpers/getConstructorName'); const oid = require('../types/objectid'); @@ -24,7 +24,7 @@ let Document; * @api public */ -function ObjectId(key, options) { +function SchemaObjectId(key, options) { const isKeyHexStr = typeof key === 'string' && key.length === 24 && /^[a-f0-9]+$/i.test(key); const suppressWarning = options && options.suppressWarning; if ((isKeyHexStr || typeof key === 'undefined') && !suppressWarning) { @@ -42,16 +42,16 @@ function ObjectId(key, options) { * * @api public */ -ObjectId.schemaName = 'ObjectId'; +SchemaObjectId.schemaName = 'ObjectId'; -ObjectId.defaultOptions = {}; +SchemaObjectId.defaultOptions = {}; /*! * Inherits from SchemaType. */ -ObjectId.prototype = Object.create(SchemaType.prototype); -ObjectId.prototype.constructor = ObjectId; -ObjectId.prototype.OptionsConstructor = SchemaObjectIdOptions; +SchemaObjectId.prototype = Object.create(SchemaType.prototype); +SchemaObjectId.prototype.constructor = SchemaObjectId; +SchemaObjectId.prototype.OptionsConstructor = SchemaObjectIdOptions; /** * Attaches a getter for all ObjectId instances @@ -71,7 +71,7 @@ ObjectId.prototype.OptionsConstructor = SchemaObjectIdOptions; * @api public */ -ObjectId.get = SchemaType.get; +SchemaObjectId.get = SchemaType.get; /** * Sets a default option for all ObjectId instances. @@ -92,9 +92,9 @@ ObjectId.get = SchemaType.get; * @api public */ -ObjectId.set = SchemaType.set; +SchemaObjectId.set = SchemaType.set; -ObjectId.setters = []; +SchemaObjectId.setters = []; /** * Adds an auto-generated ObjectId default if turnOn is true. @@ -103,7 +103,7 @@ ObjectId.setters = []; * @return {SchemaType} this */ -ObjectId.prototype.auto = function(turnOn) { +SchemaObjectId.prototype.auto = function(turnOn) { if (turnOn) { this.default(defaultId); this.set(resetId); @@ -116,13 +116,13 @@ ObjectId.prototype.auto = function(turnOn) { * ignore */ -ObjectId._checkRequired = v => isBsonType(v, 'ObjectId'); +SchemaObjectId._checkRequired = v => isBsonType(v, 'ObjectId'); /*! * ignore */ -ObjectId._cast = castObjectId; +SchemaObjectId._cast = castObjectId; /** * Get/set the function used to cast arbitrary values to objectids. @@ -147,7 +147,7 @@ ObjectId._cast = castObjectId; * @api public */ -ObjectId.cast = function cast(caster) { +SchemaObjectId.cast = function cast(caster) { if (arguments.length === 0) { return this._cast; } @@ -163,7 +163,7 @@ ObjectId.cast = function cast(caster) { * ignore */ -ObjectId._defaultCaster = v => { +SchemaObjectId._defaultCaster = v => { if (!(isBsonType(v, 'ObjectId'))) { throw new Error(v + ' is not an instance of ObjectId'); } @@ -189,7 +189,7 @@ ObjectId._defaultCaster = v => { * @api public */ -ObjectId.checkRequired = SchemaType.checkRequired; +SchemaObjectId.checkRequired = SchemaType.checkRequired; /** * Check if the given value satisfies a required validator. @@ -200,7 +200,7 @@ ObjectId.checkRequired = SchemaType.checkRequired; * @api public */ -ObjectId.prototype.checkRequired = function checkRequired(value, doc) { +SchemaObjectId.prototype.checkRequired = function checkRequired(value, doc) { if (SchemaType._isRef(this, value, doc, true)) { return !!value; } @@ -209,7 +209,7 @@ ObjectId.prototype.checkRequired = function checkRequired(value, doc) { // plugins like mongoose-float use `inherits()` for pre-ES6. const _checkRequired = typeof this.constructor.checkRequired === 'function' ? this.constructor.checkRequired() : - ObjectId.checkRequired(); + SchemaObjectId.checkRequired(); return _checkRequired(value); }; @@ -223,7 +223,7 @@ ObjectId.prototype.checkRequired = function checkRequired(value, doc) { * @api private */ -ObjectId.prototype.cast = function(value, doc, init) { +SchemaObjectId.prototype.cast = function(value, doc, init) { if (!(isBsonType(value, 'ObjectId')) && SchemaType._isRef(this, value, doc, init)) { // wait! we may need to cast this to a document if ((getConstructorName(value) || '').toLowerCase() === 'objectid') { @@ -241,7 +241,7 @@ ObjectId.prototype.cast = function(value, doc, init) { } else if (typeof this.constructor.cast === 'function') { castObjectId = this.constructor.cast(); } else { - castObjectId = ObjectId.cast(); + castObjectId = SchemaObjectId.cast(); } try { @@ -259,7 +259,7 @@ function handleSingle(val) { return this.cast(val); } -ObjectId.prototype.$conditionalHandlers = +SchemaObjectId.prototype.$conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, { $gt: handleSingle, $gte: handleSingle, @@ -278,7 +278,7 @@ function defaultId() { defaultId.$runBeforeSetters = true; function resetId(v) { - Document || (Document = require('./../document')); + Document || (Document = require('../document')); if (this instanceof Document) { if (v === void 0) { @@ -294,4 +294,4 @@ function resetId(v) { * Module exports. */ -module.exports = ObjectId; +module.exports = SchemaObjectId; diff --git a/lib/schema/string.js b/lib/schema/string.js index 5caee2e3cfc..07a08a14ba0 100644 --- a/lib/schema/string.js +++ b/lib/schema/string.js @@ -4,7 +4,7 @@ * Module dependencies. */ -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const MongooseError = require('../error/index'); const SchemaStringOptions = require('../options/SchemaStringOptions'); const castString = require('../cast/string'); @@ -244,7 +244,7 @@ SchemaString.prototype.enum = function() { const vals = this.enumValues; this.enumValidator = function(v) { - return undefined === v || ~vals.indexOf(v); + return null == v || ~vals.indexOf(v); }; this.validators.push({ validator: this.enumValidator, diff --git a/lib/schema/SubdocumentPath.js b/lib/schema/subdocument.js similarity index 80% rename from lib/schema/SubdocumentPath.js rename to lib/schema/subdocument.js index 5d4cc6db041..7a837427f31 100644 --- a/lib/schema/SubdocumentPath.js +++ b/lib/schema/subdocument.js @@ -8,7 +8,7 @@ const CastError = require('../error/cast'); const EventEmitter = require('events').EventEmitter; const ObjectExpectedError = require('../error/objectExpected'); const SchemaSubdocumentOptions = require('../options/SchemaSubdocumentOptions'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const applyDefaults = require('../helpers/document/applyDefaults'); const $exists = require('./operators/exists'); const castToNumber = require('./operators/helpers').castToNumber; @@ -21,9 +21,9 @@ const isExclusive = require('../helpers/projection/isExclusive'); const utils = require('../utils'); const InvalidSchemaOptionError = require('../error/invalidSchemaOption'); -let Subdocument; +let SubdocumentType; -module.exports = SubdocumentPath; +module.exports = SchemaSubdocument; /** * Single nested subdocument SchemaType constructor. @@ -35,12 +35,12 @@ module.exports = SubdocumentPath; * @api public */ -function SubdocumentPath(schema, path, options) { +function SchemaSubdocument(schema, path, options) { if (schema.options.timeseries) { throw new InvalidSchemaOptionError(path, 'timeseries'); } - const schemaTypeIdOption = SubdocumentPath.defaultOptions && - SubdocumentPath.defaultOptions._id; + const schemaTypeIdOption = SchemaSubdocument.defaultOptions && + SchemaSubdocument.defaultOptions._id; if (schemaTypeIdOption != null) { options = options || {}; options._id = schemaTypeIdOption; @@ -61,9 +61,9 @@ function SubdocumentPath(schema, path, options) { * ignore */ -SubdocumentPath.prototype = Object.create(SchemaType.prototype); -SubdocumentPath.prototype.constructor = SubdocumentPath; -SubdocumentPath.prototype.OptionsConstructor = SchemaSubdocumentOptions; +SchemaSubdocument.prototype = Object.create(SchemaType.prototype); +SchemaSubdocument.prototype.constructor = SchemaSubdocument; +SchemaSubdocument.prototype.OptionsConstructor = SchemaSubdocumentOptions; /*! * ignore @@ -71,11 +71,11 @@ SubdocumentPath.prototype.OptionsConstructor = SchemaSubdocumentOptions; function _createConstructor(schema, baseClass) { // lazy load - Subdocument || (Subdocument = require('../types/subdocument')); + SubdocumentType || (SubdocumentType = require('../types/subdocument')); const _embedded = function SingleNested(value, path, parent) { this.$__parent = parent; - Subdocument.apply(this, arguments); + SubdocumentType.apply(this, arguments); if (parent == null) { return; @@ -85,7 +85,7 @@ function _createConstructor(schema, baseClass) { schema._preCompile(); - const proto = baseClass != null ? baseClass.prototype : Subdocument.prototype; + const proto = baseClass != null ? baseClass.prototype : SubdocumentType.prototype; _embedded.prototype = Object.create(proto); _embedded.prototype.$__setSchema(schema); _embedded.prototype.constructor = _embedded; @@ -123,7 +123,7 @@ function _createConstructor(schema, baseClass) { * @api private */ -SubdocumentPath.prototype.$conditionalHandlers.$geoWithin = function handle$geoWithin(val, context) { +SchemaSubdocument.prototype.$conditionalHandlers.$geoWithin = function handle$geoWithin(val, context) { return { $geometry: this.castForQuery(null, val.$geometry, context) }; }; @@ -131,19 +131,19 @@ SubdocumentPath.prototype.$conditionalHandlers.$geoWithin = function handle$geoW * ignore */ -SubdocumentPath.prototype.$conditionalHandlers.$near = -SubdocumentPath.prototype.$conditionalHandlers.$nearSphere = geospatial.cast$near; +SchemaSubdocument.prototype.$conditionalHandlers.$near = +SchemaSubdocument.prototype.$conditionalHandlers.$nearSphere = geospatial.cast$near; -SubdocumentPath.prototype.$conditionalHandlers.$within = -SubdocumentPath.prototype.$conditionalHandlers.$geoWithin = geospatial.cast$within; +SchemaSubdocument.prototype.$conditionalHandlers.$within = +SchemaSubdocument.prototype.$conditionalHandlers.$geoWithin = geospatial.cast$within; -SubdocumentPath.prototype.$conditionalHandlers.$geoIntersects = +SchemaSubdocument.prototype.$conditionalHandlers.$geoIntersects = geospatial.cast$geoIntersects; -SubdocumentPath.prototype.$conditionalHandlers.$minDistance = castToNumber; -SubdocumentPath.prototype.$conditionalHandlers.$maxDistance = castToNumber; +SchemaSubdocument.prototype.$conditionalHandlers.$minDistance = castToNumber; +SchemaSubdocument.prototype.$conditionalHandlers.$maxDistance = castToNumber; -SubdocumentPath.prototype.$conditionalHandlers.$exists = $exists; +SchemaSubdocument.prototype.$conditionalHandlers.$exists = $exists; /** * Casts contents @@ -152,7 +152,7 @@ SubdocumentPath.prototype.$conditionalHandlers.$exists = $exists; * @api private */ -SubdocumentPath.prototype.cast = function(val, doc, init, priorVal, options) { +SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) { if (val && val.$isSingleNested && val.parent === doc) { return val; } @@ -201,7 +201,7 @@ SubdocumentPath.prototype.cast = function(val, doc, init, priorVal, options) { * @api private */ -SubdocumentPath.prototype.castForQuery = function($conditional, val, context, options) { +SchemaSubdocument.prototype.castForQuery = function($conditional, val, context, options) { let handler; if ($conditional != null) { handler = this.$conditionalHandlers[$conditional]; @@ -245,7 +245,7 @@ SubdocumentPath.prototype.castForQuery = function($conditional, val, context, op * @api private */ -SubdocumentPath.prototype.doValidate = function(value, fn, scope, options) { +SchemaSubdocument.prototype.doValidate = function(value, fn, scope, options) { const Constructor = getConstructor(this.caster, value); if (value && !(value instanceof Constructor)) { @@ -277,7 +277,7 @@ SubdocumentPath.prototype.doValidate = function(value, fn, scope, options) { * @api private */ -SubdocumentPath.prototype.doValidateSync = function(value, scope, options) { +SchemaSubdocument.prototype.doValidateSync = function(value, scope, options) { if (!options || !options.skipSchemaValidators) { const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope); if (schemaTypeError) { @@ -311,7 +311,7 @@ SubdocumentPath.prototype.doValidateSync = function(value, scope, options) { * @api public */ -SubdocumentPath.prototype.discriminator = function(name, schema, options) { +SchemaSubdocument.prototype.discriminator = function(name, schema, options) { options = options || {}; const value = utils.isPOJO(options) ? options.value : options; const clone = typeof options.clone === 'boolean' @@ -333,15 +333,15 @@ SubdocumentPath.prototype.discriminator = function(name, schema, options) { * ignore */ -SubdocumentPath.defaultOptions = {}; +SchemaSubdocument.defaultOptions = {}; /** - * Sets a default option for all SubdocumentPath instances. + * Sets a default option for all Subdocument instances. * * #### Example: * * // Make all numbers have option `min` equal to 0. - * mongoose.Schema.SubdocumentPath.set('required', true); + * mongoose.Schema.Subdocument.set('required', true); * * @param {String} option The option you'd like to set the value for * @param {Any} value value for option @@ -351,12 +351,12 @@ SubdocumentPath.defaultOptions = {}; * @api public */ -SubdocumentPath.set = SchemaType.set; +SchemaSubdocument.set = SchemaType.set; -SubdocumentPath.setters = []; +SchemaSubdocument.setters = []; /** - * Attaches a getter for all SubdocumentPath instances + * Attaches a getter for all Subdocument instances * * @param {Function} getter * @return {this} @@ -365,13 +365,13 @@ SubdocumentPath.setters = []; * @api public */ -SubdocumentPath.get = SchemaType.get; +SchemaSubdocument.get = SchemaType.get; /*! * ignore */ -SubdocumentPath.prototype.toJSON = function toJSON() { +SchemaSubdocument.prototype.toJSON = function toJSON() { return { path: this.path, options: this.options }; }; @@ -379,7 +379,7 @@ SubdocumentPath.prototype.toJSON = function toJSON() { * ignore */ -SubdocumentPath.prototype.clone = function() { +SchemaSubdocument.prototype.clone = function() { const options = Object.assign({}, this.options); const schematype = new this.constructor(this.schema, this.path, options); schematype.validators = this.validators.slice(); diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 9de95f6cd4d..62a9baac55b 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -5,7 +5,7 @@ 'use strict'; const MongooseBuffer = require('../types/buffer'); -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const CastError = SchemaType.CastError; const utils = require('../utils'); const handleBitwiseOperator = require('./operators/bitwise'); diff --git a/lib/schematype.js b/lib/schemaType.js similarity index 100% rename from lib/schematype.js rename to lib/schemaType.js diff --git a/lib/statemachine.js b/lib/stateMachine.js similarity index 100% rename from lib/statemachine.js rename to lib/stateMachine.js diff --git a/lib/validoptions.js b/lib/validOptions.js similarity index 100% rename from lib/validoptions.js rename to lib/validOptions.js diff --git a/lib/virtualtype.js b/lib/virtualType.js similarity index 100% rename from lib/virtualtype.js rename to lib/virtualType.js diff --git a/package.json b/package.json index b2d00055e50..49ba8faafda 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.5.0", + "version": "7.5.1", "author": "Guillermo Rauch ", "keywords": [ "mongodb", @@ -19,9 +19,9 @@ ], "license": "MIT", "dependencies": { - "bson": "^5.4.0", + "bson": "^6.0.0", "kareem": "2.5.1", - "mongodb": "5.8.1", + "mongodb": "6.0.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -30,8 +30,8 @@ "devDependencies": { "@babel/core": "7.22.11", "@babel/preset-env": "7.22.14", - "@typescript-eslint/eslint-plugin": "5.61.0", - "@typescript-eslint/parser": "5.62.0", + "@typescript-eslint/eslint-plugin": "^6.2.1", + "@typescript-eslint/parser": "^6.2.1", "acquit": "1.3.0", "acquit-ignore": "0.2.1", "acquit-require": "0.1.1", @@ -47,7 +47,7 @@ "dotenv": "16.3.1", "dox": "1.0.0", "eslint": "8.48.0", - "eslint-plugin-markdown": "^3.0.0", + "eslint-plugin-markdown": "^3.0.1", "eslint-plugin-mocha-no-only": "1.1.1", "express": "^4.18.1", "fs-extra": "~11.1.1", @@ -119,7 +119,7 @@ "main": "./index.js", "types": "./types/index.d.ts", "engines": { - "node": ">=14.20.1" + "node": ">=16.20.1" }, "bugs": { "url": "https://github.com/Automattic/mongoose/issues/new" diff --git a/test/connection.test.js b/test/connection.test.js index 30b5f640f81..f84aaa9dd82 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -6,7 +6,7 @@ const start = require('./common'); -const STATES = require('../lib/connectionstate'); +const STATES = require('../lib/connectionState'); const Q = require('q'); const assert = require('assert'); const mongodb = require('mongodb'); diff --git a/test/docs/findoneandupdate.test.js b/test/docs/findoneandupdate.test.js index e6cbaf8fe96..2fde28cd688 100644 --- a/test/docs/findoneandupdate.test.js +++ b/test/docs/findoneandupdate.test.js @@ -139,7 +139,7 @@ describe('Tutorial: findOneAndUpdate()', function() { // acquit:ignore:end }); - it('rawResult', async function() { + it('includeResultMetadata', async function() { const filter = { name: 'Will Riker' }; const update = { age: 29 }; @@ -151,7 +151,8 @@ describe('Tutorial: findOneAndUpdate()', function() { const res = await Character.findOneAndUpdate(filter, update, { new: true, upsert: true, - rawResult: true // Return the raw result from the MongoDB driver + // Return additional properties about the operation, not just the document + includeResultMetadata: true }); res.value instanceof Character; // true diff --git a/test/document.test.js b/test/document.test.js index 470ca60f8c5..6f41e2f2c9f 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -153,10 +153,11 @@ describe('document', function() { const test = new Test({ x: 'test' }); const doc = await test.save(); - await doc.deleteOne(); + const q = doc.deleteOne(); + assert.ok(q instanceof mongoose.Query, `Expected query, got ${q.constructor.name}`); + await q; const found = await Test.findOne({ _id: doc._id }); assert.strictEqual(found, null); - }); }); @@ -1944,7 +1945,7 @@ describe('document', function() { const Person = db.model('Person', personSchema); const createdPerson = await Person.create({ name: 'Hafez' }); - const removedPerson = await Person.findOneAndRemove({ _id: createdPerson._id }); + const removedPerson = await Person.findOneAndDelete({ _id: createdPerson._id }); removedPerson.isNew = true; @@ -3187,13 +3188,6 @@ describe('document', function() { assert.equal(doc.child.name, 'Anakin'); }); - it('strings of length 12 are valid oids (gh-3365)', async function() { - const schema = new Schema({ myId: mongoose.Schema.Types.ObjectId }); - const M = db.model('Test', schema); - const doc = new M({ myId: 'blablablabla' }); - await doc.validate(); - }); - it('set() empty obj unmodifies subpaths (gh-4182)', async function() { const omeletteSchema = new Schema({ topping: { @@ -9819,7 +9813,7 @@ describe('document', function() { assert.ok(doc); }); - it('Makes sure pre remove hook is executed gh-9885', async function() { + it('Makes sure pre deleteOne hook is executed (gh-9885)', async function() { const SubSchema = new Schema({ myValue: { type: String @@ -12237,19 +12231,6 @@ describe('document', function() { assert.equal(fromDb.c.x.y, 1); }); - it('can change the value of the id property on documents gh-10096', async function() { - const testSchema = new Schema({ - name: String - }); - const Test = db.model('Test', testSchema); - const doc = new Test({ name: 'Test Testerson ' }); - const oldVal = doc.id; - doc.id = '648b8aa6a97549b03835c0b3'; - await doc.save(); - assert.notEqual(oldVal, doc.id); - assert.equal(doc.id, '648b8aa6a97549b03835c0b3'); - }); - it('should allow storing keys with dots in name in mixed under nested (gh-13530)', async function() { const TestModelSchema = new mongoose.Schema({ metadata: @@ -12407,6 +12388,20 @@ describe('document', function() { const nestedProjectionDoc = await User.findOne({}, { name: 1, 'sub.propertyA': 1, 'sub.propertyB': 1 }); assert.strictEqual(nestedProjectionDoc.sub.propertyA, 'A'); }); + + it('should ignore `id` if the object contains `id` and `_id` as keys (gh-13762)', async function() { + const testSchema = new Schema({ + _id: { + type: Number + } + }); + const Test = db.model('Test', testSchema); + + const x = new Test({ _id: 1, id: 2 }); + await x.save(); + const fromDb = await Test.findById(x._id).lean(); + assert.equal(fromDb._id, 1); + }); }); describe('Check if instance function that is supplied in schema option is availabe', function() { diff --git a/test/index.test.js b/test/index.test.js index 0976aab3479..76ef810c2d1 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -466,7 +466,7 @@ describe('mongoose module:', function() { const M = mongoose.model('gh6760', schema); - const doc = new M({ testId: 'length12str0', testNum: 123, mixed: {} }); + const doc = new M({ testId: '0'.repeat(24), testNum: 123, mixed: {} }); assert.ok(doc.testId instanceof mongoose.Types.ObjectId); assert.ok(doc.testNum instanceof mongoose.Types.Decimal128); @@ -739,7 +739,7 @@ describe('mongoose module:', function() { }); it('isValidObjectId (gh-3823)', function() { - assert.ok(mongoose.isValidObjectId('0123456789ab')); + assert.ok(!mongoose.isValidObjectId('0123456789ab')); assert.ok(mongoose.isValidObjectId('5f5c2d56f6e911019ec2acdc')); assert.ok(mongoose.isValidObjectId('608DE01F32B6A93BBA314159')); assert.ok(mongoose.isValidObjectId(new mongoose.Types.ObjectId())); @@ -920,7 +920,7 @@ describe('mongoose module:', function() { const goodIdString = '1'.repeat(24); assert.deepStrictEqual(mongoose.isValidObjectId(goodIdString), true); const goodIdString2 = '1'.repeat(12); - assert.deepStrictEqual(mongoose.isValidObjectId(goodIdString2), true); + assert.deepStrictEqual(mongoose.isValidObjectId(goodIdString2), false); }); it('Allows a syncIndexes shorthand mongoose.syncIndexes (gh-10893)', async function() { const m = new mongoose.Mongoose(); diff --git a/test/model.create.test.js b/test/model.create.test.js index 3b2182e8259..d587e70ae16 100644 --- a/test/model.create.test.js +++ b/test/model.create.test.js @@ -197,6 +197,20 @@ describe('model', function() { const docs = await Test.find(); assert.equal(docs.length, 5); }); + it('should throw an error only after all the documents have finished saving gh-4628', async function() { + const testSchema = new Schema({ name: { type: String, unique: true } }); + + + const Test = db.model('gh4628Test', testSchema); + await Test.init(); + const data = []; + for (let i = 0; i < 11; i++) { + data.push({ name: 'Test' + Math.abs(i - 4) }); + } + await Test.create(data, { ordered: false }).catch(err => err); + const docs = await Test.find(); + assert.equal(docs.length, 7); // docs 1,2,3,4 should not go through 11-4 == 7 + }); it('should return the first error immediately if "aggregateErrors" is not explicitly set (ordered)', async function() { const testSchema = new Schema({ name: { type: String, required: true } }); diff --git a/test/model.discriminator.test.js b/test/model.discriminator.test.js index 6f7fd05d4a2..400cf976f63 100644 --- a/test/model.discriminator.test.js +++ b/test/model.discriminator.test.js @@ -2180,4 +2180,27 @@ describe('model', function() { assert.ok(innerBuildingsPath.schemaOptions.type.discriminators.Garage); assert.equal(innerBuildingsPath.schemaOptions.type.discriminators.Garage.discriminatorMapping.value, 'G'); }); + + it('runs base schema paths validators and setters before child schema validators and setters (gh-13794)', async function() { + const baseSchema = new Schema({ + f1: { + type: Number, + set() { + return 1; + } + } + }); + const childSchema = new Schema({ + f2: { + type: Number, + set() { + return this.f1; + } + } + }); + const Test = db.model('Test', baseSchema); + const Child = Test.discriminator('Child', childSchema); + const doc = new Child({ f1: 2, f2: 2 }); + assert.strictEqual(doc.f2, 1); + }); }); diff --git a/test/model.findOneAndDelete.test.js b/test/model.findOneAndDelete.test.js index 38986a9ca33..de77c5ceb05 100644 --- a/test/model.findOneAndDelete.test.js +++ b/test/model.findOneAndDelete.test.js @@ -350,27 +350,11 @@ describe('model: findOneAndDelete:', function() { assert.equal(doc.name, 'Test'); await Test.create({ name: 'Test' }); - let data = await Test.findOneAndDelete( + const data = await Test.findOneAndDelete( { name: 'Test' }, { includeResultMetadata: true } ); assert(data.ok); assert.equal(data.value.name, 'Test'); - - await Test.create({ name: 'Test' }); - data = await Test.findOneAndDelete( - { name: 'Test' }, - { includeResultMetadata: true, rawResult: true } - ); - assert(data.ok); - assert.equal(data.value.name, 'Test'); - - await assert.rejects( - () => Test.findOneAndDelete( - { name: 'Test' }, - { includeResultMetadata: false, rawResult: true } - ), - /Cannot set `rawResult` option when `includeResultMetadata` is false/ - ); }); }); diff --git a/test/model.findOneAndRemove.test.js b/test/model.findOneAndRemove.test.js deleted file mode 100644 index 667f7115a96..00000000000 --- a/test/model.findOneAndRemove.test.js +++ /dev/null @@ -1,349 +0,0 @@ -'use strict'; - -/** - * Test dependencies. - */ - -const start = require('./common'); - -const assert = require('assert'); - -const mongoose = start.mongoose; -const Schema = mongoose.Schema; -const ObjectId = Schema.Types.ObjectId; -const DocumentObjectId = mongoose.Types.ObjectId; - -describe('model: findOneAndRemove:', async function() { - let Comments; - let BlogPost; - let db; - - before(function() { - db = start(); - }); - - after(async function() { - await db.close(); - }); - - beforeEach(() => db.deleteModel(/.*/)); - afterEach(() => require('./util').clearTestData(db)); - afterEach(() => require('./util').stopRemainingOps(db)); - - beforeEach(function() { - Comments = new Schema(); - - Comments.add({ - title: String, - date: Date, - body: String, - comments: [Comments] - }); - - BlogPost = new Schema({ - title: String, - author: String, - slug: String, - date: Date, - meta: { - date: Date, - visitors: Number - }, - published: Boolean, - mixed: {}, - numbers: [Number], - owners: [ObjectId], - comments: [Comments] - }); - - BlogPost.virtual('titleWithAuthor') - .get(function() { - return this.get('title') + ' by ' + this.get('author'); - }) - .set(function(val) { - const split = val.split(' by '); - this.set('title', split[0]); - this.set('author', split[1]); - }); - - BlogPost.method('cool', function() { - return this; - }); - - BlogPost.static('woot', function() { - return this; - }); - - BlogPost = db.model('BlogPost', BlogPost); - }); - - it('returns the original document', async function() { - const M = BlogPost; - const title = 'remove muah'; - - const post = new M({ title: title }); - - await post.save(); - - const doc = await M.findOneAndRemove({ title: title }); - - assert.equal(post.id, doc.id); - - const gone = await M.findById(post.id); - assert.equal(gone, null); - }); - - it('options/conditions/doc are merged when no callback is passed', function(done) { - const M = BlogPost; - - const now = new Date(); - let query; - - // Model.findOneAndRemove - query = M.findOneAndRemove({ author: 'aaron' }, { select: 'author' }); - assert.equal(query._fields.author, 1); - assert.equal(query._conditions.author, 'aaron'); - - query = M.findOneAndRemove({ author: 'aaron' }); - assert.equal(query._fields, undefined); - assert.equal(query._conditions.author, 'aaron'); - - query = M.findOneAndRemove(); - assert.equal(query.options.new, undefined); - assert.equal(query._fields, undefined); - assert.equal(query._conditions.author, undefined); - - // Query.findOneAndRemove - query = M.where('author', 'aaron').findOneAndRemove({ date: now }); - assert.equal(query._fields, undefined); - assert.equal(query._conditions.date, now); - assert.equal(query._conditions.author, 'aaron'); - - query = M.find().findOneAndRemove({ author: 'aaron' }, { select: 'author' }); - assert.equal(query._fields.author, 1); - assert.equal(query._conditions.author, 'aaron'); - - query = M.find().findOneAndRemove(); - assert.equal(query._fields, undefined); - assert.equal(query._conditions.author, undefined); - done(); - }); - - it('returns the original document', async function() { - const M = BlogPost; - const title = 'remove muah pleez'; - - const post = new M({ title: title }); - await post.save(); - const doc = await M.findByIdAndRemove(post.id); - assert.equal(post.id, doc.id); - const gone = await M.findById(post.id); - assert.equal(gone, null); - }); - - it('options/conditions/doc are merged when no callback is passed', function(done) { - const M = BlogPost; - const _id = new DocumentObjectId(); - - let query; - - // Model.findByIdAndRemove - query = M.findByIdAndRemove(_id, { select: 'author' }); - assert.equal(query._fields.author, 1); - assert.equal(query._conditions._id.toString(), _id.toString()); - - query = M.findByIdAndRemove(_id.toString()); - assert.equal(query._fields, undefined); - assert.equal(query._conditions._id, _id.toString()); - - query = M.findByIdAndRemove(); - assert.equal(query.options.new, undefined); - assert.equal(query._fields, undefined); - assert.equal(query._conditions._id, undefined); - done(); - }); - - it('supports v3 select string syntax', function(done) { - const M = BlogPost; - const _id = new DocumentObjectId(); - - let query; - - query = M.findByIdAndRemove(_id, { select: 'author -title' }); - query._applyPaths(); - assert.strictEqual(1, query._fields.author); - assert.strictEqual(0, query._fields.title); - - query = M.findOneAndRemove({}, { select: 'author -title' }); - query._applyPaths(); - assert.strictEqual(1, query._fields.author); - assert.strictEqual(0, query._fields.title); - done(); - }); - - it('supports v3 select object syntax', function(done) { - const M = BlogPost; - const _id = new DocumentObjectId(); - - let query; - - query = M.findByIdAndRemove(_id, { select: { author: 1, title: 0 } }); - assert.strictEqual(1, query._fields.author); - assert.strictEqual(0, query._fields.title); - - query = M.findOneAndRemove({}, { select: { author: 1, title: 0 } }); - assert.strictEqual(1, query._fields.author); - assert.strictEqual(0, query._fields.title); - done(); - }); - - it('supports v3 sort string syntax', function(done) { - const M = BlogPost; - const _id = new DocumentObjectId(); - - let query; - - query = M.findByIdAndRemove(_id, { sort: 'author -title' }); - assert.equal(Object.keys(query.options.sort).length, 2); - assert.equal(query.options.sort.author, 1); - assert.equal(query.options.sort.title, -1); - - query = M.findOneAndRemove({}, { sort: 'author -title' }); - assert.equal(Object.keys(query.options.sort).length, 2); - assert.equal(query.options.sort.author, 1); - assert.equal(query.options.sort.title, -1); - done(); - }); - - it('supports v3 sort object syntax', function(done) { - const M = BlogPost; - const _id = new DocumentObjectId(); - - let query; - - query = M.findByIdAndRemove(_id, { sort: { author: 1, title: -1 } }); - assert.equal(Object.keys(query.options.sort).length, 2); - assert.equal(query.options.sort.author, 1); - assert.equal(query.options.sort.title, -1); - - query = M.findOneAndRemove(_id, { sort: { author: 1, title: -1 } }); - assert.equal(Object.keys(query.options.sort).length, 2); - assert.equal(query.options.sort.author, 1); - assert.equal(query.options.sort.title, -1); - done(); - }); - - it('supports population (gh-1395)', async function() { - const M = db.model('Test1', { name: String }); - const N = db.model('Test2', { a: { type: Schema.ObjectId, ref: 'Test1' }, i: Number }); - - const a = await M.create({ name: 'i am an A' }); - const b = await N.create({ a: a._id, i: 10 }); - - const doc = await N.findOneAndRemove({ _id: b._id }, { select: 'a -_id' }) - .populate('a') - .exec(); - - assert.ok(doc); - assert.equal(doc._id, undefined); - assert.ok(doc.a); - assert.equal('i am an A', doc.a.name); - }); - - it('only calls setters once (gh-6203)', async function() { - - const calls = []; - const userSchema = new mongoose.Schema({ - name: String, - foo: { - type: String, - set: function(val) { - calls.push(val); - return val + val; - } - } - }); - const Model = db.model('Test', userSchema); - - await Model.findOneAndRemove({ foo: '123' }, { name: 'bar' }); - - assert.deepEqual(calls, ['123']); - }); - - it('with orFail() (gh-9381)', function() { - const User = db.model('User', Schema({ name: String })); - - return User.findOneAndRemove({ name: 'not found' }).orFail(). - then(() => null, err => err). - then(err => { - assert.ok(err); - assert.equal(err.name, 'DocumentNotFoundError'); - }); - }); - - describe('middleware', function() { - - it('works', async function() { - const s = new Schema({ - topping: { type: String, default: 'bacon' }, - base: String - }); - - let preCount = 0; - s.pre('findOneAndRemove', function() { - ++preCount; - }); - - let postCount = 0; - s.post('findOneAndRemove', function() { - ++postCount; - }); - - const Breakfast = db.model('Test', s); - const breakfast = new Breakfast({ - base: 'eggs' - }); - - await breakfast.save(); - - const breakfast2 = await Breakfast.findOneAndRemove( - { base: 'eggs' }, - {} - ); - - assert.equal(breakfast2.base, 'eggs'); - assert.equal(preCount, 1); - assert.equal(postCount, 1); - }); - - it('works with exec() (gh-439)', async() => { - const s = new Schema({ - topping: { type: String, default: 'bacon' }, - base: String - }); - - let preCount = 0; - s.pre('findOneAndRemove', function() { - ++preCount; - }); - - let postCount = 0; - s.post('findOneAndRemove', function() { - ++postCount; - }); - - const Breakfast = db.model('Test', s); - const breakfast = new Breakfast({ - base: 'eggs' - }); - - await breakfast.save(); - - const breakfast2 = await Breakfast.findOneAndRemove({ base: 'eggs' }, {}); - - assert.equal(breakfast2.base, 'eggs'); - assert.equal(preCount, 1); - assert.equal(postCount, 1); - }); - }); -}); diff --git a/test/model.findOneAndReplace.test.js b/test/model.findOneAndReplace.test.js index 5550f198915..e8fa3764ca4 100644 --- a/test/model.findOneAndReplace.test.js +++ b/test/model.findOneAndReplace.test.js @@ -468,29 +468,12 @@ describe('model: findOneAndReplace:', function() { assert.equal(doc.ok, undefined); assert.equal(doc.name, 'Test Testerson'); - let data = await Test.findOneAndReplace( + const data = await Test.findOneAndReplace( { name: 'Test Testerson' }, { name: 'Test' }, { new: true, upsert: true, includeResultMetadata: true } ); assert(data.ok); assert.equal(data.value.name, 'Test'); - - data = await Test.findOneAndReplace( - { name: 'Test Testerson' }, - { name: 'Test' }, - { new: true, upsert: true, includeResultMetadata: true, rawResult: true } - ); - assert(data.ok); - assert.equal(data.value.name, 'Test'); - - await assert.rejects( - () => Test.findOneAndReplace( - { name: 'Test Testerson' }, - { name: 'Test' }, - { new: true, upsert: true, includeResultMetadata: false, rawResult: true } - ), - /Cannot set `rawResult` option when `includeResultMetadata` is false/ - ); }); }); diff --git a/test/model.findOneAndUpdate.test.js b/test/model.findOneAndUpdate.test.js index eee320aa968..ab3121a2581 100644 --- a/test/model.findOneAndUpdate.test.js +++ b/test/model.findOneAndUpdate.test.js @@ -666,7 +666,7 @@ describe('model: findOneAndUpdate:', function() { assert.ok(fruit instanceof mongoose.Document); }); - it('return rawResult when doing an upsert & new=false gh-7770', async function() { + it('return includeResultMetadata when doing an upsert & new=false gh-7770', async function() { const thingSchema = new Schema({ _id: String, flag: { @@ -678,13 +678,13 @@ describe('model: findOneAndUpdate:', function() { const Thing = db.model('Test', thingSchema); const key = 'some-new-id'; - const rawResult = await Thing.findOneAndUpdate({ _id: key }, { $set: { flag: false } }, { upsert: true, new: false, rawResult: true }).exec(); - assert.equal(rawResult.lastErrorObject.updatedExisting, false); + const res1 = await Thing.findOneAndUpdate({ _id: key }, { $set: { flag: false } }, { upsert: true, new: false, includeResultMetadata: true }).exec(); + assert.equal(res1.lastErrorObject.updatedExisting, false); - const rawResult2 = await Thing.findOneAndUpdate({ _id: key }, { $set: { flag: true } }, { upsert: true, new: false, rawResult: true }).exec(); - assert.equal(rawResult2.lastErrorObject.updatedExisting, true); - assert.equal(rawResult2.value._id, key); - assert.equal(rawResult2.value.flag, false); + const res2 = await Thing.findOneAndUpdate({ _id: key }, { $set: { flag: true } }, { upsert: true, new: false, includeResultMetadata: true }).exec(); + assert.equal(res2.lastErrorObject.updatedExisting, true); + assert.equal(res2.value._id, key); + assert.equal(res2.value.flag, false); }); @@ -1261,13 +1261,13 @@ describe('model: findOneAndUpdate:', function() { }); describe('bug fixes', function() { - it('passes raw result if rawResult specified (gh-4925)', async function() { + it('passes raw result if includeResultMetadata specified (gh-4925)', async function() { const testSchema = new mongoose.Schema({ test: String }); const TestModel = db.model('Test', testSchema); - const options = { upsert: true, new: true, rawResult: true }; + const options = { upsert: true, new: true, includeResultMetadata: true }; const update = { $set: { test: 'abc' } }; const res = await TestModel.findOneAndUpdate({}, update, options).exec(); @@ -2159,30 +2159,13 @@ describe('model: findOneAndUpdate:', function() { assert.equal(doc.ok, undefined); assert.equal(doc.name, 'Test Testerson'); - let data = await Test.findOneAndUpdate( + const data = await Test.findOneAndUpdate( { name: 'Test Testerson' }, { name: 'Test' }, { new: true, upsert: true, includeResultMetadata: true } ); assert(data.ok); assert.equal(data.value.name, 'Test'); - - data = await Test.findOneAndUpdate( - { name: 'Test Testerson' }, - { name: 'Test' }, - { new: true, upsert: true, includeResultMetadata: true, rawResult: true } - ); - assert(data.ok); - assert.equal(data.value.name, 'Test'); - - await assert.rejects( - () => Test.findOneAndUpdate( - { name: 'Test Testerson' }, - { name: 'Test' }, - { new: true, upsert: true, includeResultMetadata: false, rawResult: true } - ), - /Cannot set `rawResult` option when `includeResultMetadata` is false/ - ); }); it('successfully runs findOneAndUpdate with no update and versionKey set to false (gh-13783)', async function() { diff --git a/test/model.middleware.preposttypes.test.js b/test/model.middleware.preposttypes.test.js index 2a2e3323cb4..952bc901001 100644 --- a/test/model.middleware.preposttypes.test.js +++ b/test/model.middleware.preposttypes.test.js @@ -185,9 +185,9 @@ describe('pre/post hooks, type of this', function() { const MongooseDocumentMiddleware = [...MongooseDistinctDocumentMiddleware, ...MongooseQueryAndDocumentMiddleware]; const MongooseDistinctQueryMiddleware = [ - 'count', 'estimatedDocumentCount', 'countDocuments', + 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', - 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', + 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany']; const MongooseDefaultQueryMiddleware = [...MongooseDistinctQueryMiddleware, 'updateOne', 'deleteOne']; const MongooseQueryMiddleware = [...MongooseDistinctQueryMiddleware, ...MongooseQueryAndDocumentMiddleware]; @@ -278,7 +278,6 @@ describe('pre/post hooks, type of this', function() { await doc.save(); // triggers save and validate hooks // MongooseDistinctQueryMiddleware - await Doc.count().exec(); await Doc.estimatedDocumentCount().exec(); await Doc.countDocuments().exec(); await Doc.deleteMany().exec(); await Doc.create({ data: 'value' }); @@ -286,7 +285,6 @@ describe('pre/post hooks, type of this', function() { await Doc.find({}).exec(); await Doc.findOne({}).exec(); await Doc.findOneAndDelete({}).exec(); await Doc.create({ data: 'value' }); - await Doc.findOneAndRemove({}).exec(); await Doc.create({ data: 'value' }); await Doc.findOneAndReplace({}, { data: 'valueRep' }).exec(); await Doc.findOneAndUpdate({}, { data: 'valueUpd' }).exec(); await Doc.replaceOne({}, { data: 'value' }).exec(); diff --git a/test/model.middleware.test.js b/test/model.middleware.test.js index 645a6beb1d2..7747167bb4b 100644 --- a/test/model.middleware.test.js +++ b/test/model.middleware.test.js @@ -447,13 +447,13 @@ describe('model middleware', function() { await doc.deleteOne(); - assert.equal(queryPreCalled, 0); + assert.equal(queryPreCalled, 1); assert.equal(preCalled, 1); assert.equal(postCalled, 1); await Model.deleteOne(); - assert.equal(queryPreCalled, 1); + assert.equal(queryPreCalled, 2); assert.equal(preCalled, 1); assert.equal(postCalled, 1); }); diff --git a/test/model.test.js b/test/model.test.js index 013a0766688..0a0903715cc 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -1177,8 +1177,12 @@ describe('Model', function() { it('errors when id deselected (gh-3118)', async function() { await BlogPost.create({ title: 1 }, { title: 2 }); const doc = await BlogPost.findOne({ title: 1 }, { _id: 0 }); - const err = await doc.deleteOne().then(() => null, err => err); - assert.equal(err.message, 'No _id found on document!'); + try { + await doc.deleteOne(); + assert.ok(false); + } catch (err) { + assert.equal(err.message, 'No _id found on document!'); + } }); it('should not remove any records when deleting by id undefined', async function() { @@ -2319,7 +2323,7 @@ describe('Model', function() { const title = 'interop ad-hoc as promise'; const created = await BlogPost.create({ title: title }); - const query = BlogPost.count({ title: title }); + const query = BlogPost.countDocuments({ title: title }); const found = await query.exec('findOne'); assert.equal(found.id, created.id); }); @@ -5046,10 +5050,6 @@ describe('Model', function() { await Model.createCollection(); const collectionName = Model.collection.name; - // If the collection is not created, the following will throw - // MongoServerError: Collection [mongoose_test.User] not found. - await db.collection(collectionName).stats(); - await Model.create([{ name: 'alpha' }, { name: 'Zeta' }]); // Ensure that the default collation is set. Mongoose will set the @@ -5318,7 +5318,7 @@ describe('Model', function() { } ]); - const beforeExpirationCount = await Test.count({}); + const beforeExpirationCount = await Test.countDocuments({}); assert.ok(beforeExpirationCount === 12); let intervalid; @@ -5332,7 +5332,7 @@ describe('Model', function() { // in case it happens faster, to reduce test time new Promise(resolve => { intervalid = setInterval(async() => { - const count = await Test.count({}); + const count = await Test.countDocuments({}); if (count === 0) { resolve(); } @@ -5342,7 +5342,7 @@ describe('Model', function() { clearInterval(intervalid); - const afterExpirationCount = await Test.count({}); + const afterExpirationCount = await Test.countDocuments({}); assert.equal(afterExpirationCount, 0); }); @@ -5472,7 +5472,6 @@ describe('Model', function() { await doc.deleteOne({ session }); assert.equal(sessions.length, 1); assert.strictEqual(sessions[0], session); - }); it('set $session() before pre validate hooks run on bulkWrite and insertMany (gh-7769)', async function() { diff --git a/test/model.validate.test.js b/test/model.validate.test.js index d69d2d69a02..cc95e69819f 100644 --- a/test/model.validate.test.js +++ b/test/model.validate.test.js @@ -57,7 +57,7 @@ describe('model: validate: ', function() { assert.deepEqual(Object.keys(err.errors), ['comments.name']); obj = { age: '42' }; - await Model.validate(obj, ['age']); + obj = await Model.validate(obj, ['age']); assert.strictEqual(obj.age, 42); }); @@ -106,7 +106,7 @@ describe('model: validate: ', function() { const test = { docs: ['6132655f2cdb9d94eaebc09b'] }; - const err = await Test.validate(test); + const err = await Test.validate(test).then(() => null, err => err); assert.ifError(err); }); @@ -124,7 +124,7 @@ describe('model: validate: ', function() { const User = mongoose.model('User', userSchema); const user = new User({ name: 'test', nameRequired: false }); - const err = await User.validate(user).catch(err => err); + const err = await User.validate(user).then(() => null, err => err); assert.ifError(err); @@ -181,6 +181,29 @@ describe('model: validate: ', function() { await assert.rejects(async() => { await Test.validate(test, pathsOrOptions); }, { message: 'Validation failed: name: Validator failed for path `name` with value `1`' }); + }); + it('runs validation on casted paths even if cast error happened', async function() { + const Model = mongoose.model('Test', new Schema({ + invalid1: { + type: String, + validate: () => false + }, + myNumber: { + type: Number, + required: true + }, + invalid2: { + type: String, + validate: () => false + } + })); + + const err = await Model.validate({ invalid1: 'foo', myNumber: 'not a number', invalid2: 'bar' }). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors).sort(), ['invalid1', 'invalid2', 'myNumber']); + assert.equal(err.errors['myNumber'].name, 'CastError'); + assert.equal(err.errors['invalid1'].name, 'ValidatorError'); }); }); diff --git a/test/query.test.js b/test/query.test.js index 13dbd36177b..d018c5fee77 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -772,12 +772,9 @@ describe('Query', function() { query.sort({ a: 1, c: -1, b: 'asc', e: 'descending', f: 'ascending' }); assert.deepEqual(query.options.sort, { a: 1, c: -1, b: 1, e: -1, f: 1 }); - if (typeof global.Map !== 'undefined') { - query = new Query({}); - query.sort(new global.Map().set('a', 1).set('b', 1)); - assert.equal(query.options.sort.get('a'), 1); - assert.equal(query.options.sort.get('b'), 1); - } + query = new Query({}); + query.sort(new Map().set('a', 1).set('b', 1)); + assert.deepStrictEqual(query.options.sort, { a: 1, b: 1 }); query = new Query({}); let e; @@ -1916,10 +1913,9 @@ describe('Query', function() { const TestSchema = new Schema({ name: String }); const ops = [ - 'count', 'find', 'findOne', - 'findOneAndRemove', + 'findOneAndDelete', 'findOneAndUpdate', 'replaceOne', 'updateOne', diff --git a/test/query.toconstructor.test.js b/test/query.toconstructor.test.js index 1de9f78f152..1b1cadb0d6c 100644 --- a/test/query.toconstructor.test.js +++ b/test/query.toconstructor.test.js @@ -180,7 +180,7 @@ describe('Query:', function() { const query = Model.find().sort([['name', 1]]); const Query = query.toConstructor(); const q = new Query(); - assert.deepEqual(q.options.sort, [['name', 1]]); + assert.deepEqual(q.options.sort, { name: 1 }); }); }); }); diff --git a/test/queryhelpers.test.js b/test/queryhelpers.test.js index 7243086ba02..62c1b2e4afb 100644 --- a/test/queryhelpers.test.js +++ b/test/queryhelpers.test.js @@ -4,7 +4,7 @@ require('./common'); const Schema = require('../lib/schema'); const assert = require('assert'); -const queryhelpers = require('../lib/queryhelpers'); +const queryhelpers = require('../lib/queryHelpers'); describe('queryhelpers', function() { describe('applyPaths', function() { diff --git a/test/schema.select.test.js b/test/schema.select.test.js index a6a2ae29a32..70631978c76 100644 --- a/test/schema.select.test.js +++ b/test/schema.select.test.js @@ -57,10 +57,10 @@ describe('schema select option', function() { assert.equal(findUpdateDoc.isSelected('name'), false); assert.equal(findUpdateDoc.isSelected('docs.name'), false); assert.strictEqual(undefined, findUpdateDoc.name); - const findAndRemoveDoc = await Test.findOneAndRemove({ _id: doc._id }); - assert.equal(findAndRemoveDoc.isSelected('name'), false); - assert.equal(findAndRemoveDoc.isSelected('docs.name'), false); - assert.strictEqual(undefined, findAndRemoveDoc.name); + const findAndDeleteDoc = await Test.findOneAndDelete({ _id: doc._id }); + assert.equal(findAndDeleteDoc.isSelected('name'), false); + assert.equal(findAndDeleteDoc.isSelected('docs.name'), false); + assert.strictEqual(undefined, findAndDeleteDoc.name); }); it('including paths through schematype', async function() { @@ -87,10 +87,10 @@ describe('schema select option', function() { assert.strictEqual(true, findOneAndUpdateDoc.isSelected('name')); assert.strictEqual(true, findOneAndUpdateDoc.isSelected('docs.name')); assert.equal(findOneAndUpdateDoc.name, 'the included'); - const findOneAndRemoveDoc = await S.findOneAndRemove({ _id: doc._id }); - assert.strictEqual(true, findOneAndRemoveDoc.isSelected('name')); - assert.strictEqual(true, findOneAndRemoveDoc.isSelected('docs.name')); - assert.equal(findOneAndRemoveDoc.name, 'the included'); + const findOneAndDeleteDoc = await S.findOneAndDelete({ _id: doc._id }); + assert.strictEqual(true, findOneAndDeleteDoc.isSelected('name')); + assert.strictEqual(true, findOneAndDeleteDoc.isSelected('docs.name')); + assert.equal(findOneAndDeleteDoc.name, 'the included'); }); describe('overriding schematype select options', function() { @@ -210,16 +210,16 @@ describe('schema select option', function() { assert.ok(findOneAndUpdateDoc.thin); assert.ok(findOneAndUpdateDoc.docs[0].bool); }); - it('with findOneAndRemove', async function() { - const findOneAndRemoveDoc = await E.findOneAndRemove({ _id: exclusionDoc._id }).select('-name -docs.name'); - assert.equal(findOneAndRemoveDoc.isSelected('name'), false); - assert.equal(findOneAndRemoveDoc.isSelected('thin'), true); - assert.equal(findOneAndRemoveDoc.isSelected('docs.name'), false); - assert.equal(findOneAndRemoveDoc.isSelected('docs.bool'), true); - assert.strictEqual(undefined, findOneAndRemoveDoc.name); - assert.strictEqual(undefined, findOneAndRemoveDoc.docs[0].name); - assert.strictEqual(true, findOneAndRemoveDoc.thin); - assert.strictEqual(true, findOneAndRemoveDoc.docs[0].bool); + it('with findOneAndDelete', async function() { + const findOneAndDeleteDoc = await E.findOneAndDelete({ _id: exclusionDoc._id }).select('-name -docs.name'); + assert.equal(findOneAndDeleteDoc.isSelected('name'), false); + assert.equal(findOneAndDeleteDoc.isSelected('thin'), true); + assert.equal(findOneAndDeleteDoc.isSelected('docs.name'), false); + assert.equal(findOneAndDeleteDoc.isSelected('docs.bool'), true); + assert.strictEqual(undefined, findOneAndDeleteDoc.name); + assert.strictEqual(undefined, findOneAndDeleteDoc.docs[0].name); + assert.strictEqual(true, findOneAndDeleteDoc.thin); + assert.strictEqual(true, findOneAndDeleteDoc.docs[0].bool); }); }); }); diff --git a/test/schema.test.js b/test/schema.test.js index 1f7a028f1b8..495a79393a2 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -1437,7 +1437,7 @@ describe('schema', function() { } }; const s = new Schema(TestSchema, { typeKey: '$type' }); - assert.equal(s.path('action').constructor.name, 'SubdocumentPath'); + assert.equal(s.path('action').constructor.name, 'SchemaSubdocument'); assert.ok(s.path('action').schema.$implicitlyCreated); assert.equal(s.path('action.type').constructor.name, 'SchemaString'); }); @@ -2650,7 +2650,7 @@ describe('schema', function() { myStr: { type: String, cast: v => '' + v } }); - assert.equal(schema.path('myId').cast('12charstring').toHexString(), '313263686172737472696e67'); + assert.equal(schema.path('myId').cast('0'.repeat(24)).toHexString(), '0'.repeat(24)); assert.equal(schema.path('myNum').cast(3.14), 4); assert.equal(schema.path('myDate').cast('2012-06-01').getFullYear(), 2012); assert.equal(schema.path('myBool').cast('hello'), true); diff --git a/test/schema.validation.test.js b/test/schema.validation.test.js index c0a18a8835a..d70643c1825 100644 --- a/test/schema.validation.test.js +++ b/test/schema.validation.test.js @@ -827,6 +827,19 @@ describe('schema', function() { } }); + it('should allow null values for enum gh-3044', async function() { + const testSchema = new Schema({ + name: { + type: String, + enum: ['test'] + } + }); + const Test = mongoose.model('allow-null' + random(), testSchema); + const a = new Test({ name: null }); + const err = await a.validate().then(() => null, err => err); + assert.equal(err, null); + }); + it('should allow an array of subdocuments with enums (gh-3521)', async function() { const coolSchema = new Schema({ votes: [{ diff --git a/test/schematype.cast.test.js b/test/schematype.cast.test.js index acf2658f3af..77f28e2d9a7 100644 --- a/test/schematype.cast.test.js +++ b/test/schematype.cast.test.js @@ -29,6 +29,9 @@ describe('SchemaType.cast() (gh-7045)', function() { class CustomObjectId extends Schema.ObjectId {} CustomObjectId.cast(v => { + if (v === 'special') { + return original.objectid('0'.repeat(24)); + } assert.ok(v == null || (typeof v === 'string' && v.length === 24)); return original.objectid(v); }); @@ -38,7 +41,7 @@ describe('SchemaType.cast() (gh-7045)', function() { let threw = false; try { - objectid.cast('12charstring'); + baseObjectId.cast('special'); } catch (error) { threw = true; assert.equal(error.name, 'CastError'); @@ -46,7 +49,7 @@ describe('SchemaType.cast() (gh-7045)', function() { assert.doesNotThrow(function() { objectid.cast('000000000000000000000000'); - baseObjectId.cast('12charstring'); + objectid.cast('special'); baseObjectId.cast('000000000000000000000000'); }); diff --git a/test/schematype.test.js b/test/schematype.test.js index 13eb8c5dce4..38691d645ae 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -235,7 +235,7 @@ describe('schematype', function() { }); const typesToTest = Object.values(mongoose.SchemaTypes). - filter(t => t.name !== 'SubdocumentPath' && t.name !== 'DocumentArrayPath'); + filter(t => t.name !== 'SchemaSubdocument' && t.name !== 'SchemaDocumentArray'); typesToTest.forEach((type) => { it(type.name + ', when given a default option, set its', () => { diff --git a/test/types/middleware.preposttypes.test.ts b/test/types/middleware.preposttypes.test.ts index 6ee1ec35017..006adb2dd6f 100644 --- a/test/types/middleware.preposttypes.test.ts +++ b/test/types/middleware.preposttypes.test.ts @@ -468,51 +468,6 @@ schema.post('findOneAndDelete', { document: false, query: false }, function(res) expectNotType>(res); }); -schema.pre('findOneAndRemove', function() { - expectType>(this); -}); - -schema.post('findOneAndRemove', function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('findOneAndRemove', { document: false, query: true }, function() { - expectType>(this); -}); - -schema.post('findOneAndRemove', { document: false, query: true }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('findOneAndRemove', { document: true, query: true }, function() { - expectType>(this); -}); - -schema.post('findOneAndRemove', { document: true, query: true }, function(res) { - expectType>(this); - expectNotType>(res); -}); - -schema.pre('findOneAndRemove', { document: true, query: false }, function() { - expectType(this); -}); - -schema.post('findOneAndRemove', { document: true, query: false }, function(res) { - expectType(this); - expectNotType>(res); -}); - -schema.pre('findOneAndRemove', { document: false, query: false }, function() { - expectType(this); -}); - -schema.post('findOneAndRemove', { document: false, query: false }, function(res) { - expectType(this); - expectNotType>(res); -}); - schema.pre('findOneAndReplace', function() { expectType>(this); }); @@ -828,92 +783,92 @@ schema.post(['save', 'init'], { document: false, query: false }, function(res) { expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: true }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: false }, function() { expectType(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: true, query: false }, function(res) { expectType(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); @@ -963,92 +918,92 @@ schema.post(['save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function() { expectType|HydratedDocument>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function() { expectType|HydratedDocument>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: true }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function() { expectType>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: false }, function(res) { expectType>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function() { expectType|HydratedDocument>(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: true, query: true }, function(res) { expectType|HydratedDocument>(this); expectNotType>(res); }); -schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { +schema.pre(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function() { expectType(this); }); -schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndRemove', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { +schema.post(['count', 'estimatedDocumentCount', 'countDocuments', 'deleteMany', 'distinct', 'find', 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'replaceOne', 'updateMany', 'save', 'init', 'updateOne', 'deleteOne', 'validate'], { document: false, query: false }, function(res) { expectType(this); expectNotType>(res); }); diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index efca12618a5..ff5f7c18cba 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -115,10 +115,10 @@ Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { upsert: true, new: Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { upsert: true, returnOriginal: false }).then((res: ITest) => { res.name = 'test4'; }); -Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { rawResult: true }).then((res: any) => { +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { includeResultMetadata: true }).then((res: any) => { console.log(res.ok); }); -Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { new: true, upsert: true, rawResult: true }).then((res: any) => { +Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { new: true, upsert: true, includeResultMetadata: true }).then((res: any) => { console.log(res.ok); }); @@ -295,8 +295,9 @@ async function gh11306(): Promise { // 3. Create a Model. const MyModel = model('User', schema); - expectType(await MyModel.distinct('name')); - expectType(await MyModel.distinct('name')); + expectType(await MyModel.distinct('notThereInSchema')); + expectType(await MyModel.distinct('name')); + expectType(await MyModel.distinct<'overrideTest', number>('overrideTest')); } function autoTypedQuery() { @@ -422,7 +423,7 @@ async function gh11602(): Promise { const updateResult = await ModelType.findOneAndUpdate(query, { $inc: { occurence: 1 } }, { upsert: true, returnDocument: 'after', - rawResult: true + includeResultMetadata: true }); expectError(updateResult.lastErrorObject?.modifiedCount); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 16f58ed938b..fc0204e2a71 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1218,3 +1218,15 @@ function gh13800() { expectError(this.someOtherField); }); } + +async function gh13797() { + interface IUser { + name: string; + } + new Schema({ name: { type: String, required: function() { + expectType(this); return true; + } } }); + new Schema({ name: { type: String, default: function() { + expectType(this); return ''; + } } }); +} diff --git a/test/utils.test.js b/test/utils.test.js index fe321b424c6..648512fb877 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -8,7 +8,7 @@ const start = require('./common'); const MongooseBuffer = require('../lib/types/buffer'); const ObjectId = require('../lib/types/objectid'); -const StateMachine = require('../lib/statemachine'); +const StateMachine = require('../lib/stateMachine'); const assert = require('assert'); const utils = require('../lib/utils'); diff --git a/types/index.d.ts b/types/index.d.ts index 840a3634e16..1bd4b0a7085 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -236,7 +236,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions | ResolveSchemaOptions); + constructor(definition?: SchemaDefinition, EnforcedDocType> | DocType, options?: SchemaOptions | ResolveSchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; @@ -300,7 +300,7 @@ declare module 'mongoose' { methods: { [F in keyof TInstanceMethods]: TInstanceMethods[F] } & AnyObject; /** The original object passed to the schema constructor */ - obj: SchemaDefinition>; + obj: SchemaDefinition, EnforcedDocType>; /** Gets/sets schema paths. */ path>(path: string): ResultType; @@ -484,26 +484,26 @@ declare module 'mongoose' { ? DateSchemaDefinition : (Function | string); - export type SchemaDefinitionProperty = SchemaDefinitionWithBuiltInClass | - SchemaTypeOptions | + export type SchemaDefinitionProperty = SchemaDefinitionWithBuiltInClass | + SchemaTypeOptions | typeof SchemaType | Schema | Schema[] | - SchemaTypeOptions>[] | + SchemaTypeOptions, EnforcedDocType>[] | Function[] | - SchemaDefinition | - SchemaDefinition>[] | + SchemaDefinition | + SchemaDefinition, EnforcedDocType>[] | typeof Schema.Types.Mixed | - MixedSchemaTypeOptions; + MixedSchemaTypeOptions; - export type SchemaDefinition = T extends undefined + export type SchemaDefinition = T extends undefined ? { [path: string]: SchemaDefinitionProperty; } - : { [path in keyof T]?: SchemaDefinitionProperty; }; + : { [path in keyof T]?: SchemaDefinitionProperty; }; export type AnyArray = T[] | ReadonlyArray; export type ExtractMongooseArray = T extends Types.Array ? AnyArray> : T; - export interface MixedSchemaTypeOptions extends SchemaTypeOptions { + export interface MixedSchemaTypeOptions extends SchemaTypeOptions { type: typeof Schema.Types.Mixed; } diff --git a/types/middlewares.d.ts b/types/middlewares.d.ts index 43ca1974b81..2d305252908 100644 --- a/types/middlewares.d.ts +++ b/types/middlewares.d.ts @@ -5,7 +5,7 @@ declare module 'mongoose' { type MongooseDistinctDocumentMiddleware = 'save' | 'init' | 'validate'; type MongooseDocumentMiddleware = MongooseDistinctDocumentMiddleware | MongooseQueryAndDocumentMiddleware; - type MongooseDistinctQueryMiddleware = 'count' | 'estimatedDocumentCount' | 'countDocuments' | 'deleteMany' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndReplace' | 'findOneAndUpdate' | 'replaceOne' | 'updateMany'; + type MongooseDistinctQueryMiddleware = 'count' | 'estimatedDocumentCount' | 'countDocuments' | 'deleteMany' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndReplace' | 'findOneAndUpdate' | 'replaceOne' | 'updateMany'; type MongooseDefaultQueryMiddleware = MongooseDistinctQueryMiddleware | 'updateOne' | 'deleteOne'; type MongooseQueryMiddleware = MongooseDistinctQueryMiddleware | MongooseQueryAndDocumentMiddleware; diff --git a/types/models.d.ts b/types/models.d.ts index 2b0a5989445..6af56a5656d 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -443,8 +443,11 @@ declare module 'mongoose' { translateAliases(raw: any): any; /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct(field: string, filter?: FilterQuery): QueryWithHelpers< - Array, + distinct( + field: DocKey, + filter?: FilterQuery + ): QueryWithHelpers< + Array, THydratedDocumentType, TQueryHelpers, TRawDocType, @@ -548,7 +551,7 @@ declare module 'mongoose' { findByIdAndUpdate( id: mongodb.ObjectId | any, update: UpdateQuery, - options: QueryOptions & { rawResult: true } + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndUpdate'>; findByIdAndUpdate( id: mongodb.ObjectId | any, @@ -586,12 +589,6 @@ declare module 'mongoose' { options?: QueryOptions | null ): QueryWithHelpers; - /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove( - filter?: FilterQuery, - options?: QueryOptions | null - ): QueryWithHelpers; - /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ findOneAndReplace( filter: FilterQuery, @@ -607,7 +604,7 @@ declare module 'mongoose' { findOneAndReplace( filter: FilterQuery, replacement: TRawDocType | AnyObject, - options: QueryOptions & { rawResult: true } + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndReplace'>; findOneAndReplace( filter: FilterQuery, @@ -640,7 +637,7 @@ declare module 'mongoose' { findOneAndUpdate( filter: FilterQuery, update: UpdateQuery, - options: QueryOptions & { rawResult: true } + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndUpdate'>; findOneAndUpdate( filter: FilterQuery, diff --git a/types/query.d.ts b/types/query.d.ts index 3963481a20b..008f82d2d9d 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -124,9 +124,9 @@ declare module 'mongoose' { overwriteDiscriminatorKey?: boolean; projection?: ProjectionType; /** - * if true, returns the raw result from the MongoDB driver + * if true, returns the full ModifyResult rather than just the document */ - rawResult?: boolean; + includeResultMetadata?: boolean; readPreference?: string | mongodb.ReadPreferenceMode; /** * An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. @@ -317,10 +317,10 @@ declare module 'mongoose' { deleteOne(): QueryWithHelpers; /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct( - field: string, + distinct( + field: DocKey, filter?: FilterQuery - ): QueryWithHelpers, DocType, THelpers, RawDocType, 'distinct'>; + ): QueryWithHelpers, DocType, THelpers, RawDocType, 'distinct'>; /** Specifies a `$elemMatch` query condition. When called with one argument, the most recent path passed to `where()` is used. */ elemMatch(path: K, val: any): this; @@ -392,17 +392,11 @@ declare module 'mongoose' { options?: QueryOptions | null ): QueryWithHelpers; - /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove( - filter?: FilterQuery, - options?: QueryOptions | null - ): QueryWithHelpers; - /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ findOneAndUpdate( filter: FilterQuery, update: UpdateQuery, - options: QueryOptions & { rawResult: true } + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, DocType, THelpers, RawDocType, 'findOneAndUpdate'>; findOneAndUpdate( filter: FilterQuery, @@ -439,7 +433,7 @@ declare module 'mongoose' { findByIdAndUpdate( id: mongodb.ObjectId | any, update: UpdateQuery, - options: QueryOptions & { rawResult: true } + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers; findByIdAndUpdate( id: mongodb.ObjectId | any, diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index 5f7514a8e03..088bc27c598 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -39,7 +39,7 @@ declare module 'mongoose' { type DefaultType = T extends Schema.Types.Mixed ? any : Partial>; - class SchemaTypeOptions { + class SchemaTypeOptions { type?: T extends string ? StringSchemaDefinition : T extends number ? NumberSchemaDefinition : @@ -48,12 +48,12 @@ declare module 'mongoose' { T extends Map ? SchemaDefinition : T extends Buffer ? SchemaDefinition : T extends Types.ObjectId ? ObjectIdSchemaDefinition : - T extends Types.ObjectId[] ? AnyArray | AnyArray> : - T extends object[] ? (AnyArray> | AnyArray>> | AnyArray>>) : - T extends string[] ? AnyArray | AnyArray> : - T extends number[] ? AnyArray | AnyArray> : - T extends boolean[] ? AnyArray | AnyArray> : - T extends Function[] ? AnyArray | AnyArray>> : + T extends Types.ObjectId[] ? AnyArray | AnyArray> : + T extends object[] ? (AnyArray> | AnyArray>> | AnyArray, EnforcedDocType>>) : + T extends string[] ? AnyArray | AnyArray> : + T extends number[] ? AnyArray | AnyArray> : + T extends boolean[] ? AnyArray | AnyArray> : + T extends Function[] ? AnyArray | AnyArray, EnforcedDocType>> : T | typeof SchemaType | Schema | SchemaDefinition | Function | AnyArray; /** Defines a virtual with the given name that gets/sets this path. */ @@ -74,13 +74,13 @@ declare module 'mongoose' { * path cannot be set to a nullish value. If a function, Mongoose calls the * function and only checks for nullish values if the function returns a truthy value. */ - required?: boolean | (() => boolean) | [boolean, string] | [() => boolean, string]; + required?: boolean | ((this: EnforcedDocType) => boolean) | [boolean, string] | [(this: EnforcedDocType) => boolean, string]; /** * The default value for this path. If a function, Mongoose executes the function * and uses the return value as the default. */ - default?: DefaultType | ((this: any, doc: any) => DefaultType) | null; + default?: DefaultType | ((this: EnforcedDocType, doc: any) => DefaultType) | null; /** * The model that `populate()` should use if populating this path.