diff --git a/.eslintrc.js b/.eslintrc.js index 6fb3cb375eb..1bf13a95d2c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,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 f31cc79c56a..ca2e4538cc8 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 5b091034ec6..3030634c8bc 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 07cffa69145..ae81736608d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +8.0.0-rc0 / 2023-10-24 +====================== + * BREAKING CHANGE: use MongoDB node driver 6, drop support for rawResult option and findOneAndRemove() #13753 + * BREAKING CHANGE: apply minimize by default when updating document #13843 + * BREAKING CHANGE: remove `id` setter #13784 + * BREAKING CHANGE: remove overwrite option for updateOne(), findOneAndUpdate(), etc. #13989 #13578 + * BREAKING CHANGE: make model.prototype.deleteOne() return query, not promise #13660 #13369 + * BREAKING CHANGE: remove `Model.count()`, `Query.prototype.count()` #13618 #13598 + * BREAKING CHANGE: allow null values for string enum #13620 #3044 + * BREAKING CHANGE: make base schema paths come before discriminator schema paths when running setters, validators, etc. #13846 #13794 + * BREAKING CHANGE: make Model.validate() use Model.castObject() to cast, and return casted copy of object instead of modifying in place #13287 #12668 + * BREAKING CHANGE: make internal file names all camelCase #13950 #13909 #13308 + * BREAKING CHANGE: make create() wait for all documents to finish inserting or error out before throwing an error if ordered = false #13621 #4628 + * BREAKING CHANGE: refactor out `mongoose/lib/mongoose.js` file to allow importing Mongoose without MongoDB driver #13905 + * BREAKING CHANGE(types): allow `null` for optional fields #13901 + * BREAKING CHANGE(types): infer return types types for Model.distinct and Query.distinct #13836 [kaulshashank](https://github.com/kaulshashank) + 7.6.3 / 2023-10-17 ================== * fix(populate): handle multiple spaces when specifying paths to populate using space-delimited paths #13984 #13951 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..f5f6d44f236 --- /dev/null +++ b/docs/migrating_to_8.md @@ -0,0 +1,300 @@ +# 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) +* [`Document.prototype.deleteOne()` now returns a query](#document-prototype-deleteone-now-returns-a-query) +* [MongoDB Node Driver 6.0](#mongodb-node-driver-6) +* [Removed `findOneAndRemove()`](#removed-findoneandremove) +* [Removed `count()`](#removed-count) +* [Removed id Setter](#removed-id-setter) +* [`null` is valid for non-required string enums](#null-is-valid-for-non-required-string-enums) +* [Apply minimize when `save()` updates an existing document](#apply-minimize-when-save-updates-an-existing-document) +* [Apply base schema paths before discriminator paths](#apply-base-schema-paths-before-discriminator-paths) +* [Removed `overwrite` option for `findOneAndUpdate()`](#removed-overwrite-option-for-findoneandupdate) +* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert) +* [`create()` waits until all saves are done before throwing any error](#create-waits-until-all-saves-are-done-before-throwing-any-error) +* [`Model.validate()` returns copy of object](#model-validate-returns-copy-of-object) +* [Allow `null` For Optional Fields in TypeScript](#allow-null-for-optional-fields-in-typescript) +* [Model constructor properties are all optional in TypeScript](#model-constructor-properties-are-all-optional-in-typescript) +* [Infer `distinct()` return types from schema](#infer-distinct-return-types-from-schema) + +

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`. + +

Document.prototype.deleteOne now returns a query

+ +In Mongoose 7, `doc.deleteOne()` returned a promise that resolved to `doc`. +In Mongoose 8, `doc.deleteOne()` returns a query for easier chaining, as well as consistency with `doc.updateOne()`. + +```javascript +const numberOne = await Character.findOne({ name: 'Will Riker' }); + +// In Mongoose 7, q is a Promise that resolves to `numberOne` +// In Mongoose 8, q is a Query. +const q = numberOne.deleteOne(); + +// In Mongoose 7, `res === numberOne` +// In Mongoose 8, `res` is a `DeleteResult`. +const res = await q; +``` + +

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 count()

+ +`Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` 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. + +

null is valid for non-required string enums

+ +Before Mongoose 8, setting a string path with an `enum` to `null` would lead to a validation error, even if that path wasn't `required`. +In Mongoose 8, it is valid to set a string path to `null` if `required` is not set, even with `enum`. + +```javascript +const schema = new Schema({ + status: { + type: String, + enum: ['on', 'off'] + } +}); +const Test = mongoose.model('Test', schema); + +// Works fine in Mongoose 8 +// Throws a `ValidationError` in Mongoose 7 +await Test.create({ status: null }); +``` + +

Apply minimize when save() updates an existing document

+ +In Mongoose 7, Mongoose would only apply minimize when saving a new document, not when updating an existing document. + +```javascript +const schema = new Schema({ + nested: { + field1: Number + } +}); +const Test = mongoose.model('Test', schema); + +// Both Mongoose 7 and Mongoose 8 strip out empty objects when saving +// a new document in MongoDB by default +const { _id } = await Test.create({ nested: {} }); +let rawDoc = await Test.findById(_id).lean(); +rawDoc.nested; // undefined + +// Mongoose 8 will also strip out empty objects when saving an +// existing document in MongoDB +const doc = await Test.findById(_id); +doc.nested = {}; +doc.markModified('nested'); +await doc.save(); + +let rawDoc = await Test.findById(_id).lean(); +rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7 +``` + +

Apply base schema paths before discriminator paths

+ +This means that, in Mongoose 8, getters and setters on discriminator paths run *after* getters and setters on base paths. +In Mongoose 7, getters and setters on discriminator paths ran *before* getters and setters on base paths. + +```javascript + +const schema = new Schema({ + name: { + type: String, + get(v) { + console.log('Base schema getter'); + return v; + } + } +}); + +const Test = mongoose.model('Test', schema); +const D = Test.discriminator('D', new Schema({ + otherProp: { + type: String, + get(v) { + console.log('Discriminator schema getter'); + return v; + } + } +})); + +const doc = new D({ name: 'test', otherProp: 'test' }); +// In Mongoose 8, prints "Base schema getter" followed by "Discriminator schema getter" +// In Mongoose 7, prints "Discriminator schema getter" followed by "Base schema getter" +console.log(doc.toObject({ getters: true })); +``` + +

Removed overwrite option for findOneAndUpdate()

+ +Mongoose 7 and earlier supported an `overwrite` option for `findOneAndUpdate()`, `updateOne()`, and `update()`. +Before Mongoose 7, `overwrite` would skip wrapping the `update` parameter in `$set`, which meant that `findOneAndUpdate()` and `update()` would overwrite the matched document. +In Mongoose 7, setting `overwrite` would convert `findOneAndUpdate()` to `findOneAndReplace()` and `updateOne()` to `replaceOne()` to retain backwards compatibility. + +In Mongoose 8, the `overwrite` option is no longer supported. +If you want to overwrite the entire document, use `findOneAndReplace()` or `replaceOne()`. + +

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. + +

create() waits until all saves are done before throwing any error

+ +In Mongoose 7, `create()` would immediately throw if any `save()` threw an error by default. +Mongoose 8 instead waits for all `save()` calls to finish before throwing the first error that occurred. +So `create()` will throw the same error in both Mongoose 7 and Mongoose 8, Mongoose 8 just may take longer to throw the error. + +```javascript +const schema = new Schema({ + name: { + type: String, + enum: ['Badger', 'Mushroom'] + } +}); +schema.pre('save', async function() { + await new Promise(resolve => setTimeout(resolve, 1000)); +}); +const Test = mongoose.model('Test', schema); + +const err = await Test.create([ + { name: 'Badger' }, + { name: 'Mushroom' }, + { name: 'Cow' } +]).then(() => null, err => err); +err; // ValidationError + +// In Mongoose 7, there would be 0 documents, because `Test.create()` +// would throw before 'Badger' and 'Mushroom' are inserted +// In Mongoose 8, there will be 2 documents. `Test.create()` waits until +// 'Badger' and 'Mushroom' are inserted before throwing. +await Test.countDocuments(); +``` + +

Model.validate() returns copy of object

+ +In Mongoose 7, `Model.validate()` would potentially modify the passed in object. +Mongoose 8 instead copies the passed in object first. + +```javascript +const schema = new Schema({ answer: Number }); +const Test = mongoose.model('Test', schema); + +const obj = { answer: '42' }; +const res = Test.validate(obj); + +typeof obj.answer; // 'string' in Mongoose 8, 'number' in Mongoose 7 +typeof res.answer; // 'number' in both Mongoose 7 and Mongoose 8 +``` + +

Allow null For Optional Fields in TypeScript

+ +In Mongoose 8, automatically inferred schema types in TypeScript allow `null` for optional fields. +In Mongoose 7, optional fields only allowed `undefined`, not `null`. + +```typescript +const schema = new Schema({ name: String }); +const TestModel = model('Test', schema); + +const doc = new TestModel(); + +// In Mongoose 8, this type is `string | null | undefined`. +// In Mongoose 7, this type is `string | undefined` +doc.name; +``` + +

Model constructor properties are all optional in TypeScript

+ +In Mongoose 8, no properties are required on model constructors by default. + +```ts +import {Schema, model, Model} from 'mongoose'; + +interface IDocument { + name: string; + createdAt: Date; + updatedAt: Date; +} + +const documentSchema = new Schema( + { name: { type: String, required: true } }, + { timestamps: true } +); + +const TestModel = model('Document', documentSchema); + +// Would throw a compile error in Mongoose 7, compiles in Mongoose 8 +const newDoc = new TestModel({ + name: 'Foo' +}); + +// Explicitly pass generic param to constructor to specify the expected +// type of the model constructor param. The following will cause TS +// to complain about missing `createdAt` and `updatedAt` in Mongoose 8. +const newDoc2 = new TestModel({ + name: 'Foo' +}); +``` + +

Infer distinct() return types from schema

+ +```ts +interface User { + name: string; + email: string; + avatar?: string; +} +const schema = new Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String +}); + +// Works in Mongoose 8. Compile error in Mongoose 7. +const names: string[] = await MyModel.distinct('name'); +``` 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..9a68b55916f 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -15,30 +15,30 @@ const files = [ 'lib/document.js', 'lib/model.js', 'lib/query.js', - 'lib/cursor/QueryCursor.js', + 'lib/cursor/queryCursor.js', 'lib/aggregate.js', - 'lib/cursor/AggregationCursor.js', - 'lib/schematype.js', - 'lib/virtualtype.js', + 'lib/cursor/aggregationCursor.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', - 'lib/options/SchemaBufferOptions.js', - 'lib/options/SchemaDateOptions.js', - 'lib/options/SchemaNumberOptions.js', - 'lib/options/SchemaObjectIdOptions.js', - 'lib/options/SchemaStringOptions.js', - 'lib/types/DocumentArray/methods/index.js', + 'lib/options/schemaTypeOptions.js', + 'lib/options/schemaArrayOptions.js', + 'lib/options/schemaBufferOptions.js', + 'lib/options/schemaDateOptions.js', + 'lib/options/schemaNumberOptions.js', + 'lib/options/schemaObjectIdOptions.js', + 'lib/options/schemaStringOptions.js', + 'lib/types/documentArray/methods/index.js', 'lib/types/subdocument.js', - 'lib/types/ArraySubdocument.js', + 'lib/types/arraySubdocument.js', 'lib/types/buffer.js', 'lib/types/decimal128.js', 'lib/types/map.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/aggregate.js b/lib/aggregate.js index a160f57420c..7128d57b1fd 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -4,7 +4,7 @@ * Module dependencies */ -const AggregationCursor = require('./cursor/AggregationCursor'); +const AggregationCursor = require('./cursor/aggregationCursor'); const MongooseError = require('./error/mongooseError'); const Query = require('./query'); const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption'); 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..cf1077cc987 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -4,10 +4,10 @@ * Module dependencies. */ -const ChangeStream = require('./cursor/ChangeStream'); +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/AggregationCursor.js b/lib/cursor/aggregationCursor.js similarity index 100% rename from lib/cursor/AggregationCursor.js rename to lib/cursor/aggregationCursor.js diff --git a/lib/cursor/ChangeStream.js b/lib/cursor/changeStream.js similarity index 100% rename from lib/cursor/ChangeStream.js rename to lib/cursor/changeStream.js diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/queryCursor.js similarity index 99% rename from lib/cursor/QueryCursor.js rename to lib/cursor/queryCursor.js index 60024b0d785..9ecbb27cdd1 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 e0d5674faf8..0d5ae95a81e 100644 --- a/lib/document.js +++ b/lib/document.js @@ -33,8 +33,9 @@ const isExclusive = require('./helpers/projection/isExclusive'); const inspect = require('util').inspect; 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'); @@ -1636,7 +1637,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru */ Document.prototype.$__set = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { - Embedded = Embedded || require('./types/ArraySubdocument'); + Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); @@ -3539,7 +3540,7 @@ Document.prototype.$__setSchema = function(schema) { */ Document.prototype.$__getArrayPathsToValidate = function() { - DocumentArray || (DocumentArray = require('./types/DocumentArray')); + DocumentArray || (DocumentArray = require('./types/documentArray')); // validate all document arrays. return this.$__.activePaths @@ -3568,8 +3569,8 @@ Document.prototype.$__getArrayPathsToValidate = function() { */ Document.prototype.$getAllSubdocs = function() { - DocumentArray || (DocumentArray = require('./types/DocumentArray')); - Embedded = Embedded || require('./types/ArraySubdocument'); + DocumentArray || (DocumentArray = require('./types/documentArray')); + Embedded = Embedded || require('./types/arraySubdocument'); function docReducer(doc, seed, path) { let val = doc; @@ -3949,42 +3950,6 @@ Document.prototype.toObject = function(options) { return this.$toObject(options); }; -/** - * Minimizes an object, removing undefined values and empty objects - * - * @param {Object} object to minimize - * @return {Object} - * @api private - */ - -function minimize(obj) { - const keys = Object.keys(obj); - let i = keys.length; - let hasKeys; - let key; - let val; - - while (i--) { - key = keys[i]; - val = obj[key]; - - if (utils.isPOJO(val)) { - obj[key] = minimize(val); - } - - if (undefined === obj[key]) { - delete obj[key]; - continue; - } - - hasKeys = true; - } - - return hasKeys - ? obj - : undefined; -} - /*! * Applies virtuals properties to `json`. */ 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 6d1878558b9..cfdfd075b89 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/minimize.js b/lib/helpers/minimize.js new file mode 100644 index 00000000000..902309f7bb8 --- /dev/null +++ b/lib/helpers/minimize.js @@ -0,0 +1,41 @@ +'use strict'; + +const { isPOJO } = require('../utils'); + +module.exports = minimize; + +/** + * Minimizes an object, removing undefined values and empty objects + * + * @param {Object} object to minimize + * @return {Object|undefined} + * @api private + */ + +function minimize(obj) { + const keys = Object.keys(obj); + let i = keys.length; + let hasKeys; + let key; + let val; + + while (i--) { + key = keys[i]; + val = obj[key]; + + if (isPOJO(val)) { + obj[key] = minimize(val); + } + + if (undefined === obj[key]) { + delete obj[key]; + continue; + } + + hasKeys = true; + } + + return hasKeys + ? obj + : undefined; +} 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/model/castBulkWrite.js b/lib/helpers/model/castBulkWrite.js index 71d9150f848..1aeecc58bfc 100644 --- a/lib/helpers/model/castBulkWrite.js +++ b/lib/helpers/model/castBulkWrite.js @@ -99,7 +99,6 @@ module.exports = function castBulkWrite(originalModel, op, options) { op['updateOne']['update'] = castUpdate(model.schema, op['updateOne']['update'], { strict: strict, - overwrite: false, upsert: op['updateOne'].upsert }, model, op['updateOne']['filter']); } catch (error) { @@ -157,7 +156,6 @@ module.exports = function castBulkWrite(originalModel, op, options) { op['updateMany']['update'] = castUpdate(model.schema, op['updateMany']['update'], { strict: strict, - overwrite: false, upsert: op['updateMany'].upsert }, model, op['updateMany']['filter']); } catch (error) { 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/populate/assignVals.js b/lib/helpers/populate/assignVals.js index 92f0ebecd05..84206ee4bce 100644 --- a/lib/helpers/populate/assignVals.js +++ b/lib/helpers/populate/assignVals.js @@ -1,7 +1,7 @@ 'use strict'; const MongooseMap = require('../../types/map'); -const SkipPopulateValue = require('./SkipPopulateValue'); +const SkipPopulateValue = require('./skipPopulateValue'); const assignRawDocsToIdStructure = require('./assignRawDocsToIdStructure'); const get = require('../get'); const getVirtual = require('./getVirtual'); diff --git a/lib/helpers/populate/createPopulateQueryFilter.js b/lib/helpers/populate/createPopulateQueryFilter.js index acfeee62ae0..47509a35658 100644 --- a/lib/helpers/populate/createPopulateQueryFilter.js +++ b/lib/helpers/populate/createPopulateQueryFilter.js @@ -1,6 +1,6 @@ 'use strict'; -const SkipPopulateValue = require('./SkipPopulateValue'); +const SkipPopulateValue = require('./skipPopulateValue'); const parentPaths = require('../path/parentPaths'); const { trusted } = require('../query/trusted'); const hasDollarKeys = require('../query/hasDollarKeys'); diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js index dd70d436203..333c08942d3 100644 --- a/lib/helpers/populate/getModelsMapForPopulate.js +++ b/lib/helpers/populate/getModelsMapForPopulate.js @@ -1,7 +1,7 @@ 'use strict'; const MongooseError = require('../../error/index'); -const SkipPopulateValue = require('./SkipPopulateValue'); +const SkipPopulateValue = require('./skipPopulateValue'); const clone = require('../clone'); const get = require('../get'); const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue'); diff --git a/lib/helpers/populate/SkipPopulateValue.js b/lib/helpers/populate/skipPopulateValue.js similarity index 100% rename from lib/helpers/populate/SkipPopulateValue.js rename to lib/helpers/populate/skipPopulateValue.js 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/castUpdate.js b/lib/helpers/query/castUpdate.js index 25fbb456ea1..794df077495 100644 --- a/lib/helpers/query/castUpdate.js +++ b/lib/helpers/query/castUpdate.js @@ -38,7 +38,6 @@ const mongodbUpdateOperators = new Set([ * @param {Schema} schema * @param {Object} obj * @param {Object} [options] - * @param {Boolean} [options.overwrite] defaults to false * @param {Boolean|String} [options.strict] defaults to true * @param {Query} context passed to setters * @return {Boolean} true iff the update is non-empty @@ -61,7 +60,7 @@ module.exports = function castUpdate(schema, obj, options, context, filter) { return obj; } - if (options.upsert && !options.overwrite) { + if (options.upsert) { moveImmutableProperties(schema, obj, context); } @@ -70,13 +69,11 @@ module.exports = function castUpdate(schema, obj, options, context, filter) { const ret = {}; let val; let hasDollarKey = false; - const overwrite = options.overwrite; filter = filter || {}; while (i--) { const op = ops[i]; - // if overwrite is set, don't do any of the special $set stuff - if (!mongodbUpdateOperators.has(op) && !overwrite) { + if (!mongodbUpdateOperators.has(op)) { // fix up $set sugar if (!ret.$set) { if (obj.$set) { @@ -106,10 +103,8 @@ module.exports = function castUpdate(schema, obj, options, context, filter) { if (val && typeof val === 'object' && !Buffer.isBuffer(val) && - (!overwrite || mongodbUpdateOperators.has(op))) { + mongodbUpdateOperators.has(op)) { walkUpdatePath(schema, val, op, options, context, filter); - } else if (overwrite && ret && typeof ret === 'object') { - walkUpdatePath(schema, ret, '$set', options, context, filter); } else { const msg = 'Invalid atomic update value for ' + op + '. ' + 'Expected an object, received ' + typeof val; @@ -239,7 +234,6 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { } if (op !== '$setOnInsert' && - !options.overwrite && handleImmutable(schematype, strict, obj, key, prefix + key, context)) { continue; } @@ -334,7 +328,6 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { // You can use `$setOnInsert` with immutable keys if (op !== '$setOnInsert' && - !options.overwrite && handleImmutable(schematype, strict, obj, key, prefix + key, context)) { continue; } 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/helpers/timestamps/setupTimestamps.js b/lib/helpers/timestamps/setupTimestamps.js index 78293deb904..f6ba12b98b6 100644 --- a/lib/helpers/timestamps/setupTimestamps.js +++ b/lib/helpers/timestamps/setupTimestamps.js @@ -102,7 +102,7 @@ module.exports = function setupTimestamps(schema, timestamps) { updatedAt, this.getUpdate(), this._mongooseOptions, - this.schema + replaceOps.has(this.op) ); applyTimestampsToChildren(now, this.getUpdate(), this.model.schema); next(); diff --git a/lib/helpers/update/applyTimestampsToUpdate.js b/lib/helpers/update/applyTimestampsToUpdate.js index b48febafb69..e8d3217fbb9 100644 --- a/lib/helpers/update/applyTimestampsToUpdate.js +++ b/lib/helpers/update/applyTimestampsToUpdate.js @@ -12,10 +12,9 @@ module.exports = applyTimestampsToUpdate; * ignore */ -function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options) { +function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options, isReplace) { const updates = currentUpdate; let _updates = updates; - const overwrite = get(options, 'overwrite', false); const timestamps = get(options, 'timestamps', true); // Support skipping timestamps at the query level, see gh-6980 @@ -26,7 +25,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio const skipCreatedAt = timestamps != null && timestamps.createdAt === false; const skipUpdatedAt = timestamps != null && timestamps.updatedAt === false; - if (overwrite) { + if (isReplace) { if (currentUpdate && currentUpdate.$set) { currentUpdate = currentUpdate.$set; updates.$set = {}; diff --git a/lib/index.js b/lib/index.js index 3e7bf3709ef..6247d6a4d5c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,1275 +6,8 @@ require('./driver').set(require('./drivers/node-mongodb-native')); -const Document = require('./document'); -const EventEmitter = require('events').EventEmitter; -const Kareem = require('kareem'); -const Schema = require('./schema'); -const SchemaType = require('./schematype'); -const SchemaTypes = require('./schema/index'); -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'); -const applyPlugins = require('./helpers/schema/applyPlugins'); -const builtinPlugins = require('./plugins'); -const driver = require('./driver'); -const legacyPluralize = require('./helpers/pluralize'); -const utils = require('./utils'); -const pkg = require('../package.json'); -const cast = require('./cast'); +const mongoose = require('./mongoose'); -const Aggregate = require('./aggregate'); -const trusted = require('./helpers/query/trusted').trusted; -const sanitizeFilter = require('./helpers/query/sanitizeFilter'); -const isBsonType = require('./helpers/isBsonType'); -const MongooseError = require('./error/mongooseError'); -const SetOptionError = require('./error/setOptionError'); +mongoose.Mongoose.prototype.mongo = require('mongodb'); -const defaultMongooseSymbol = Symbol.for('mongoose:default'); - -require('./helpers/printJestWarning'); - -const objectIdHexRegexp = /^[0-9A-Fa-f]{24}$/; - -/** - * Mongoose constructor. - * - * The exports object of the `mongoose` module is an instance of this class. - * Most apps will only use this one instance. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * mongoose instanceof mongoose.Mongoose; // true - * - * // Create a new Mongoose instance with its own `connect()`, `set()`, `model()`, etc. - * const m = new mongoose.Mongoose(); - * - * @api public - * @param {Object} options see [`Mongoose#set()` docs](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.set()) - */ -function Mongoose(options) { - this.connections = []; - this.nextConnectionId = 0; - this.models = {}; - this.events = new EventEmitter(); - this.__driver = driver.get(); - // default global options - this.options = Object.assign({ - pluralization: true, - autoIndex: true, - autoCreate: true - }, options); - const createInitialConnection = utils.getOption('createInitialConnection', this.options); - if (createInitialConnection == null || createInitialConnection) { - const conn = this.createConnection(); // default connection - conn.models = this.models; - } - - if (this.options.pluralization) { - this._pluralize = legacyPluralize; - } - - // If a user creates their own Mongoose instance, give them a separate copy - // of the `Schema` constructor so they get separate custom types. (gh-6933) - if (!options || !options[defaultMongooseSymbol]) { - const _this = this; - this.Schema = function() { - this.base = _this; - return Schema.apply(this, arguments); - }; - this.Schema.prototype = Object.create(Schema.prototype); - - Object.assign(this.Schema, Schema); - this.Schema.base = this; - this.Schema.Types = Object.assign({}, Schema.Types); - } else { - // Hack to work around babel's strange behavior with - // `import mongoose, { Schema } from 'mongoose'`. Because `Schema` is not - // an own property of a Mongoose global, Schema will be undefined. See gh-5648 - for (const key of ['Schema', 'model']) { - this[key] = Mongoose.prototype[key]; - } - } - this.Schema.prototype.base = this; - - Object.defineProperty(this, 'plugins', { - configurable: false, - enumerable: true, - writable: false, - value: Object.values(builtinPlugins).map(plugin => ([plugin, { deduplicate: true }])) - }); -} - -Mongoose.prototype.cast = cast; -/** - * Expose connection states for user-land - * - * @memberOf Mongoose - * @property STATES - * @api public - */ -Mongoose.prototype.STATES = STATES; - -/** - * Expose connection states for user-land - * - * @memberOf Mongoose - * @property ConnectionStates - * @api public - */ -Mongoose.prototype.ConnectionStates = STATES; - -/** - * Object with `get()` and `set()` containing the underlying driver this Mongoose instance - * uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions - * like `find()`. - * - * @deprecated - * @memberOf Mongoose - * @property driver - * @api public - */ - -Mongoose.prototype.driver = driver; - -/** - * Overwrites the current driver used by this Mongoose instance. A driver is a - * Mongoose-specific interface that defines functions like `find()`. - * - * @memberOf Mongoose - * @method setDriver - * @api public - */ - -Mongoose.prototype.setDriver = function setDriver(driver) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - if (_mongoose.__driver === driver) { - return _mongoose; - } - - const openConnection = _mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected); - if (openConnection) { - const msg = 'Cannot modify Mongoose driver if a connection is already open. ' + - 'Call `mongoose.disconnect()` before modifying the driver'; - throw new MongooseError(msg); - } - _mongoose.__driver = driver; - - const Connection = driver.Connection; - _mongoose.connections = [new Connection(_mongoose)]; - _mongoose.connections[0].models = _mongoose.models; - - return _mongoose; -}; - -/** - * Sets mongoose options - * - * `key` can be used a object to set multiple options at once. - * If a error gets thrown for one option, other options will still be evaluated. - * - * #### Example: - * - * mongoose.set('test', value) // sets the 'test' option to `value` - * - * mongoose.set('debug', true) // enable logging collection methods + arguments to the console/file - * - * mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments - * - * mongoose.set({ debug: true, autoIndex: false }); // set multiple options at once - * - * Currently supported options are: - * - `allowDiskUse`: Set to `true` to set `allowDiskUse` to true to all aggregation operations by default. - * - `applyPluginsToChildSchemas`: `true` by default. Set to false to skip applying global plugins to child schemas - * - `applyPluginsToDiscriminators`: `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema. - * - `autoCreate`: Set to `true` to make Mongoose call [`Model.createCollection()`](https://mongoosejs.com/docs/api/model.html#Model.createCollection()) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist. - * - `autoIndex`: `true` by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance. - * - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models - * - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds). - * - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model. - * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. - * - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis. - * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable) which means you can update the `createdAt` - * - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query - * - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter. - * - `overwriteModels`: Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`. - * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information. - * - `runValidators`: `false` by default. Set to true to enable [update validators](https://mongoosejs.com/docs/validation.html#update-validators) for all validators by default. - * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.sanitizeFilter()) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. - * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. - * - `strict`: `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. - * - `strictQuery`: `false` by default. May be `false`, `true`, or `'throw'`. Sets the default [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas. - * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toJSON()), for determining how Mongoose documents get serialized by `JSON.stringify()` - * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) - * - * @param {String|Object} key The name of the option or a object of multiple key-value pairs - * @param {String|Function|Boolean} value The value of the option, unused if "key" is a object - * @returns {Mongoose} The used Mongoose instnace - * @api public - */ - -Mongoose.prototype.set = function(key, value) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - if (arguments.length === 1 && typeof key !== 'object') { - if (VALID_OPTIONS.indexOf(key) === -1) { - const error = new SetOptionError(); - error.addError(key, new SetOptionError.SetOptionInnerError(key)); - throw error; - } - - return _mongoose.options[key]; - } - - let options = {}; - - if (arguments.length === 2) { - options = { [key]: value }; - } - - if (arguments.length === 1 && typeof key === 'object') { - options = key; - } - - // array for errors to collect all errors for all key-value pairs, like ".validate" - let error = undefined; - - for (const [optionKey, optionValue] of Object.entries(options)) { - if (VALID_OPTIONS.indexOf(optionKey) === -1) { - if (!error) { - error = new SetOptionError(); - } - error.addError(optionKey, new SetOptionError.SetOptionInnerError(optionKey)); - continue; - } - - _mongoose.options[optionKey] = optionValue; - - if (optionKey === 'objectIdGetter') { - if (optionValue) { - Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', { - enumerable: false, - configurable: true, - get: function() { - return this; - } - }); - } else { - delete mongoose.Types.ObjectId.prototype._id; - } - } - } - - if (error) { - throw error; - } - - return _mongoose; -}; - -/** - * Gets mongoose options - * - * #### Example: - * - * mongoose.get('test') // returns the 'test' value - * - * @param {String} key - * @method get - * @api public - */ - -Mongoose.prototype.get = Mongoose.prototype.set; - -/** - * Creates a Connection instance. - * - * Each `connection` instance maps to a single database. This method is helpful when managing multiple db connections. - * - * - * _Options passed take precedence over options included in connection strings._ - * - * #### Example: - * - * // with mongodb:// URI - * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database'); - * - * // and options - * const opts = { db: { native_parser: true }} - * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', opts); - * - * // replica sets - * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database'); - * - * // and options - * const opts = { replset: { strategy: 'ping', rs_name: 'testSet' }} - * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database', opts); - * - * // initialize now, connect later - * db = mongoose.createConnection(); - * db.openUri('127.0.0.1', 'database', port, [opts]); - * - * @param {String} uri mongodb URI to connect to - * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below. - * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. - * @param {String} [options.dbName] The name of the database you want to use. If not provided, Mongoose uses the database name from connection string. - * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. - * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. - * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. - * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary). - * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - * @param {Number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. Defaults to 0, which means Node.js will not time out the socket due to inactivity. A socket may be inactive because of either no activity or a long-running operation. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. - * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. - * @return {Connection} the created Connection object. Connections are not thenable, so you can't do `await mongoose.createConnection()`. To await use `mongoose.createConnection(uri).asPromise()` instead. - * @api public - */ - -Mongoose.prototype.createConnection = function(uri, options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - const Connection = _mongoose.__driver.Connection; - const conn = new Connection(_mongoose); - _mongoose.connections.push(conn); - _mongoose.nextConnectionId++; - _mongoose.events.emit('createConnection', conn); - - if (arguments.length > 0) { - conn.openUri(uri, { ...options, _fireAndForget: true }); - } - - return conn; -}; - -/** - * Opens the default mongoose connection. - * - * #### Example: - * - * mongoose.connect('mongodb://user:pass@127.0.0.1:port/database'); - * - * // replica sets - * const uri = 'mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/mydatabase'; - * mongoose.connect(uri); - * - * // with options - * mongoose.connect(uri, options); - * - * // optional callback that gets fired when initial connection completed - * const uri = 'mongodb://nonexistent.domain:27000'; - * mongoose.connect(uri, function(error) { - * // if error is truthy, the initial connection failed. - * }) - * - * @param {String} uri mongodb URI to connect to - * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below. - * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. - * @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. - * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. - * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. - * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. - * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). - * @param {Number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. - * @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds). - * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. - * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. - * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary). - * @param {Number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. `socketTimeoutMS` defaults to 0, which means Node.js will not time out the socket due to inactivity. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. - * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. - * @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection. - * @param {Function} [callback] - * @see Mongoose#createConnection https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection() - * @api public - * @return {Promise} resolves to `this` if connection succeeded - */ - -Mongoose.prototype.connect = async function connect(uri, options) { - if (typeof options === 'function' || (arguments.length >= 3 && typeof arguments[2] === 'function')) { - throw new MongooseError('Mongoose.prototype.connect() no longer accepts a callback'); - } - - const _mongoose = this instanceof Mongoose ? this : mongoose; - const conn = _mongoose.connection; - - return conn.openUri(uri, options).then(() => _mongoose); -}; - -/** - * Runs `.close()` on all connections in parallel. - * - * @return {Promise} resolves when all connections are closed, or rejects with the first error that occurred. - * @api public - */ - -Mongoose.prototype.disconnect = async function disconnect() { - if (arguments.length >= 1 && typeof arguments[0] === 'function') { - throw new MongooseError('Mongoose.prototype.disconnect() no longer accepts a callback'); - } - - const _mongoose = this instanceof Mongoose ? this : mongoose; - - const remaining = _mongoose.connections.length; - if (remaining <= 0) { - return; - } - await Promise.all(_mongoose.connections.map(conn => conn.close())); -}; - -/** - * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions) - * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/), - * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). - * - * Calling `mongoose.startSession()` is equivalent to calling `mongoose.connection.startSession()`. - * Sessions are scoped to a connection, so calling `mongoose.startSession()` - * starts a session on the [default mongoose connection](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connection). - * - * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession) - * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency - * @param {Function} [callback] - * @return {Promise} promise that resolves to a MongoDB driver `ClientSession` - * @api public - */ - -Mongoose.prototype.startSession = function() { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - return _mongoose.connection.startSession.apply(_mongoose.connection, arguments); -}; - -/** - * Getter/setter around function for pluralizing collection names. - * - * @param {Function|null} [fn] overwrites the function used to pluralize collection names - * @return {Function|null} the current function used to pluralize collection names, defaults to the legacy function from `mongoose-legacy-pluralize`. - * @api public - */ - -Mongoose.prototype.pluralize = function(fn) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - if (arguments.length > 0) { - _mongoose._pluralize = fn; - } - return _mongoose._pluralize; -}; - -/** - * Defines a model or retrieves it. - * - * Models defined on the `mongoose` instance are available to all connection - * created by the same `mongoose` instance. - * - * If you call `mongoose.model()` with twice the same name but a different schema, - * you will get an `OverwriteModelError`. If you call `mongoose.model()` with - * the same name and same schema, you'll get the same schema back. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * - * // define an Actor model with this mongoose instance - * const schema = new Schema({ name: String }); - * mongoose.model('Actor', schema); - * - * // create a new connection - * const conn = mongoose.createConnection(..); - * - * // create Actor model - * const Actor = conn.model('Actor', schema); - * conn.model('Actor') === Actor; // true - * conn.model('Actor', schema) === Actor; // true, same schema - * conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name - * - * // This throws an `OverwriteModelError` because the schema is different. - * conn.model('Actor', new Schema({ name: String })); - * - * _When no `collection` argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use `mongoose.pluralize()`, or set your schemas collection name option._ - * - * #### Example: - * - * const schema = new Schema({ name: String }, { collection: 'actor' }); - * - * // or - * - * schema.set('collection', 'actor'); - * - * // or - * - * const collectionName = 'actor' - * const M = mongoose.model('Actor', schema, collectionName) - * - * @param {String|Function} name model name or class extending Model - * @param {Schema} [schema] the schema to use. - * @param {String} [collection] name (optional, inferred from model name) - * @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist. - * @api public - */ - -Mongoose.prototype.model = function(name, schema, collection, options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - if (typeof schema === 'string') { - collection = schema; - schema = false; - } - - if (arguments.length === 1) { - const model = _mongoose.models[name]; - if (!model) { - throw new MongooseError.MissingSchemaError(name); - } - return model; - } - - if (utils.isObject(schema) && !(schema instanceof Schema)) { - schema = new Schema(schema); - } - if (schema && !(schema instanceof Schema)) { - throw new Error('The 2nd parameter to `mongoose.model()` should be a ' + - 'schema or a POJO'); - } - - // handle internal options from connection.model() - options = options || {}; - - const originalSchema = schema; - if (schema) { - if (_mongoose.get('cloneSchemas')) { - schema = schema.clone(); - } - _mongoose._applyPlugins(schema); - } - - // connection.model() may be passing a different schema for - // an existing model name. in this case don't read from cache. - const overwriteModels = _mongoose.options.hasOwnProperty('overwriteModels') ? - _mongoose.options.overwriteModels : - options.overwriteModels; - if (_mongoose.models.hasOwnProperty(name) && options.cache !== false && overwriteModels !== true) { - if (originalSchema && - originalSchema.instanceOfSchema && - originalSchema !== _mongoose.models[name].schema) { - throw new _mongoose.Error.OverwriteModelError(name); - } - if (collection && collection !== _mongoose.models[name].collection.name) { - // subclass current model with alternate collection - const model = _mongoose.models[name]; - schema = model.prototype.schema; - const sub = model.__subclass(_mongoose.connection, schema, collection); - // do not cache the sub model - return sub; - } - return _mongoose.models[name]; - } - if (schema == null) { - throw new _mongoose.Error.MissingSchemaError(name); - } - - const model = _mongoose._model(name, schema, collection, options); - _mongoose.connection.models[name] = model; - _mongoose.models[name] = model; - - return model; -}; - -/*! - * ignore - */ - -Mongoose.prototype._model = function(name, schema, collection, options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - let model; - if (typeof name === 'function') { - model = name; - name = model.name; - if (!(model.prototype instanceof Model)) { - throw new _mongoose.Error('The provided class ' + name + ' must extend Model'); - } - } - - if (schema) { - if (_mongoose.get('cloneSchemas')) { - schema = schema.clone(); - } - _mongoose._applyPlugins(schema); - } - - // Apply relevant "global" options to the schema - if (schema == null || !('pluralization' in schema.options)) { - schema.options.pluralization = _mongoose.options.pluralization; - } - - if (!collection) { - collection = schema.get('collection') || - utils.toCollectionName(name, _mongoose.pluralize()); - } - - const connection = options.connection || _mongoose.connection; - model = _mongoose.Model.compile(model || name, schema, collection, connection, _mongoose); - // Errors handled internally, so safe to ignore error - model.init().catch(function $modelInitNoop() {}); - - connection.emit('model', model); - - if (schema._applyDiscriminators != null) { - for (const disc of schema._applyDiscriminators.keys()) { - model.discriminator(disc, schema._applyDiscriminators.get(disc)); - } - } - - return model; -}; - -/** - * Removes the model named `name` from the default connection, if it exists. - * You can use this function to clean up any models you created in your tests to - * prevent OverwriteModelErrors. - * - * Equivalent to `mongoose.connection.deleteModel(name)`. - * - * #### Example: - * - * mongoose.model('User', new Schema({ name: String })); - * console.log(mongoose.model('User')); // Model object - * mongoose.deleteModel('User'); - * console.log(mongoose.model('User')); // undefined - * - * // Usually useful in a Mocha `afterEach()` hook - * afterEach(function() { - * mongoose.deleteModel(/.+/); // Delete every model - * }); - * - * @api public - * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp. - * @return {Mongoose} this - */ - -Mongoose.prototype.deleteModel = function(name) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - _mongoose.connection.deleteModel(name); - delete _mongoose.models[name]; - return _mongoose; -}; - -/** - * Returns an array of model names created on this instance of Mongoose. - * - * #### Note: - * - * _Does not include names of models created using `connection.model()`._ - * - * @api public - * @return {Array} - */ - -Mongoose.prototype.modelNames = function() { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - const names = Object.keys(_mongoose.models); - return names; -}; - -/** - * Applies global plugins to `schema`. - * - * @param {Schema} schema - * @api private - */ - -Mongoose.prototype._applyPlugins = function(schema, options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - options = options || {}; - options.applyPluginsToDiscriminators = _mongoose.options && _mongoose.options.applyPluginsToDiscriminators || false; - options.applyPluginsToChildSchemas = typeof (_mongoose.options && _mongoose.options.applyPluginsToChildSchemas) === 'boolean' ? - _mongoose.options.applyPluginsToChildSchemas : - true; - applyPlugins(schema, _mongoose.plugins, options, '$globalPluginsApplied'); -}; - -/** - * Declares a global plugin executed on all Schemas. - * - * Equivalent to calling `.plugin(fn)` on each Schema you create. - * - * @param {Function} fn plugin callback - * @param {Object} [opts] optional options - * @return {Mongoose} this - * @see plugins https://mongoosejs.com/docs/plugins.html - * @api public - */ - -Mongoose.prototype.plugin = function(fn, opts) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - - _mongoose.plugins.push([fn, opts]); - return _mongoose; -}; - -/** - * The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connections). - * - * #### Example: - * - * const mongoose = require('mongoose'); - * mongoose.connect(...); - * mongoose.connection.on('error', cb); - * - * This is the connection used by default for every model created using [mongoose.model](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()). - * - * To create a new connection, use [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()). - * - * @memberOf Mongoose - * @instance - * @property {Connection} connection - * @api public - */ - -Mongoose.prototype.__defineGetter__('connection', function() { - return this.connections[0]; -}); - -Mongoose.prototype.__defineSetter__('connection', function(v) { - if (v instanceof this.__driver.Connection) { - this.connections[0] = v; - this.models = v.models; - } -}); - -/** - * An array containing all [connections](connection.html) associated with this - * Mongoose instance. By default, there is 1 connection. Calling - * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) adds a connection - * to this array. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * mongoose.connections.length; // 1, just the default connection - * mongoose.connections[0] === mongoose.connection; // true - * - * mongoose.createConnection('mongodb://127.0.0.1:27017/test'); - * mongoose.connections.length; // 2 - * - * @memberOf Mongoose - * @instance - * @property {Array} connections - * @api public - */ - -Mongoose.prototype.connections; - -/** - * An integer containing the value of the next connection id. Calling - * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) increments - * this value. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * mongoose.createConnection(); // id `0`, `nextConnectionId` becomes `1` - * mongoose.createConnection(); // id `1`, `nextConnectionId` becomes `2` - * mongoose.connections[0].destroy() // Removes connection with id `0` - * mongoose.createConnection(); // id `2`, `nextConnectionId` becomes `3` - * - * @memberOf Mongoose - * @instance - * @property {Number} nextConnectionId - * @api private - */ - -Mongoose.prototype.nextConnectionId; - -/** - * The Mongoose Aggregate constructor - * - * @method Aggregate - * @api public - */ - -Mongoose.prototype.Aggregate = Aggregate; - -/** - * The Mongoose Collection constructor - * - * @memberOf Mongoose - * @instance - * @method Collection - * @api public - */ - -Object.defineProperty(Mongoose.prototype, 'Collection', { - get: function() { - return this.__driver.Collection; - }, - set: function(Collection) { - this.__driver.Collection = Collection; - } -}); - -/** - * The Mongoose [Connection](https://mongoosejs.com/docs/api/connection.html#Connection()) constructor - * - * @memberOf Mongoose - * @instance - * @method Connection - * @api public - */ - -Object.defineProperty(Mongoose.prototype, 'Connection', { - get: function() { - return this.__driver.Connection; - }, - set: function(Connection) { - if (Connection === this.__driver.Connection) { - return; - } - - this.__driver.Connection = Connection; - } -}); - -/** - * The Mongoose version - * - * #### Example: - * - * console.log(mongoose.version); // '5.x.x' - * - * @property version - * @api public - */ - -Mongoose.prototype.version = pkg.version; - -/** - * The Mongoose constructor - * - * The exports of the mongoose module is an instance of this class. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * const mongoose2 = new mongoose.Mongoose(); - * - * @method Mongoose - * @api public - */ - -Mongoose.prototype.Mongoose = Mongoose; - -/** - * The Mongoose [Schema](https://mongoosejs.com/docs/api/schema.html#Schema()) constructor - * - * #### Example: - * - * const mongoose = require('mongoose'); - * const Schema = mongoose.Schema; - * const CatSchema = new Schema(..); - * - * @method Schema - * @api public - */ - -Mongoose.prototype.Schema = Schema; - -/** - * The Mongoose [SchemaType](https://mongoosejs.com/docs/api/schematype.html#SchemaType()) constructor - * - * @method SchemaType - * @api public - */ - -Mongoose.prototype.SchemaType = SchemaType; - -/** - * The various Mongoose SchemaTypes. - * - * #### Note: - * - * _Alias of mongoose.Schema.Types for backwards compatibility._ - * - * @property SchemaTypes - * @see Schema.SchemaTypes https://mongoosejs.com/docs/schematypes.html - * @api public - */ - -Mongoose.prototype.SchemaTypes = Schema.Types; - -/** - * The Mongoose [VirtualType](https://mongoosejs.com/docs/api/virtualtype.html#VirtualType()) constructor - * - * @method VirtualType - * @api public - */ - -Mongoose.prototype.VirtualType = VirtualType; - -/** - * The various Mongoose Types. - * - * #### Example: - * - * const mongoose = require('mongoose'); - * const array = mongoose.Types.Array; - * - * #### Types: - * - * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays) - * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers) - * - [Embedded](https://mongoosejs.com/docs/schematypes.html#schemas) - * - [DocumentArray](https://mongoosejs.com/docs/api/documentarraypath.html) - * - [Decimal128](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.Decimal128) - * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) - * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) - * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) - * - * Using this exposed access to the `ObjectId` type, we can construct ids on demand. - * - * const ObjectId = mongoose.Types.ObjectId; - * const id1 = new ObjectId; - * - * @property Types - * @api public - */ - -Mongoose.prototype.Types = Types; - -/** - * The Mongoose [Query](https://mongoosejs.com/docs/api/query.html#Query()) constructor. - * - * @method Query - * @api public - */ - -Mongoose.prototype.Query = Query; - -/** - * The Mongoose [Model](https://mongoosejs.com/docs/api/model.html#Model()) constructor. - * - * @method Model - * @api public - */ - -Mongoose.prototype.Model = Model; - -/** - * The Mongoose [Document](https://mongoosejs.com/docs/api/document.html#Document()) constructor. - * - * @method Document - * @api public - */ - -Mongoose.prototype.Document = Document; - -/** - * The Mongoose DocumentProvider constructor. Mongoose users should not have to - * use this directly - * - * @method DocumentProvider - * @api public - */ - -Mongoose.prototype.DocumentProvider = require('./document_provider'); - -/** - * The Mongoose ObjectId [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that should be - * [MongoDB ObjectIds](https://www.mongodb.com/docs/manual/reference/method/ObjectId/). - * Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId` - * instead. - * - * #### Example: - * - * const childSchema = new Schema({ parentId: mongoose.ObjectId }); - * - * @property ObjectId - * @api public - */ - -Mongoose.prototype.ObjectId = SchemaTypes.ObjectId; - -/** - * Returns true if Mongoose can cast the given value to an ObjectId, or - * false otherwise. - * - * #### Example: - * - * mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true - * mongoose.isValidObjectId('0123456789ab'); // true - * mongoose.isValidObjectId(6); // true - * mongoose.isValidObjectId(new User({ name: 'test' })); // true - * - * mongoose.isValidObjectId({ test: 42 }); // false - * - * @method isValidObjectId - * @param {Any} v - * @returns {boolean} true if `v` is something Mongoose can coerce to an ObjectId - * @api public - */ - -Mongoose.prototype.isValidObjectId = function(v) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - return _mongoose.Types.ObjectId.isValid(v); -}; - -/** - * Returns true if the given value is a Mongoose ObjectId (using `instanceof`) or if the - * given value is a 24 character hex string, which is the most commonly used string representation - * of an ObjectId. - * - * This function is similar to `isValidObjectId()`, but considerably more strict, because - * `isValidObjectId()` will return `true` for _any_ value that Mongoose can convert to an - * ObjectId. That includes Mongoose documents, any string of length 12, and any number. - * `isObjectIdOrHexString()` returns true only for `ObjectId` instances or 24 character hex - * strings, and will return false for numbers, documents, and strings of length 12. - * - * #### Example: - * - * mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true - * mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true - * - * mongoose.isObjectIdOrHexString('0123456789ab'); // false - * mongoose.isObjectIdOrHexString(6); // false - * mongoose.isObjectIdOrHexString(new User({ name: 'test' })); // false - * mongoose.isObjectIdOrHexString({ test: 42 }); // false - * - * @method isObjectIdOrHexString - * @param {Any} v - * @returns {boolean} true if `v` is an ObjectId instance _or_ a 24 char hex string - * @api public - */ - -Mongoose.prototype.isObjectIdOrHexString = function(v) { - return isBsonType(v, 'ObjectId') || (typeof v === 'string' && objectIdHexRegexp.test(v)); -}; - -/** - * - * Syncs all the indexes for the models registered with this connection. - * - * @param {Object} options - * @param {Boolean} options.continueOnError `false` by default. If set to `true`, mongoose will not throw an error if one model syncing failed, and will return an object where the keys are the names of the models, and the values are the results/errors for each model. - * @return {Promise} Returns a Promise, when the Promise resolves the value is a list of the dropped indexes. - */ -Mongoose.prototype.syncIndexes = function(options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; - return _mongoose.connection.syncIndexes(options); -}; - -/** - * The Mongoose Decimal128 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that should be - * [128-bit decimal floating points](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html). - * Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128` - * instead. - * - * #### Example: - * - * const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 }); - * - * @property Decimal128 - * @api public - */ - -Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; - -/** - * The Mongoose Mixed [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that Mongoose's change tracking, casting, - * and validation should ignore. - * - * #### Example: - * - * const schema = new Schema({ arbitrary: mongoose.Mixed }); - * - * @property Mixed - * @api public - */ - -Mongoose.prototype.Mixed = SchemaTypes.Mixed; - -/** - * The Mongoose Date [SchemaType](https://mongoosejs.com/docs/schematypes.html). - * - * #### Example: - * - * const schema = new Schema({ test: Date }); - * schema.path('test') instanceof mongoose.Date; // true - * - * @property Date - * @api public - */ - -Mongoose.prototype.Date = SchemaTypes.Date; - -/** - * The Mongoose Number [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that Mongoose should cast to numbers. - * - * #### Example: - * - * const schema = new Schema({ num: mongoose.Number }); - * // Equivalent to: - * const schema = new Schema({ num: 'number' }); - * - * @property Number - * @api public - */ - -Mongoose.prototype.Number = SchemaTypes.Number; - -/** - * The [MongooseError](https://mongoosejs.com/docs/api/error.html#Error()) constructor. - * - * @method Error - * @api public - */ - -Mongoose.prototype.Error = require('./error/index'); -Mongoose.prototype.MongooseError = require('./error/mongooseError'); - -/** - * Mongoose uses this function to get the current time when setting - * [timestamps](https://mongoosejs.com/docs/guide.html#timestamps). You may stub out this function - * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. - * - * @method now - * @returns Date the current time - * @api public - */ - -Mongoose.prototype.now = function now() { return new Date(); }; - -/** - * The Mongoose CastError constructor - * - * @method CastError - * @param {String} type The name of the type - * @param {Any} value The value that failed to cast - * @param {String} path The path `a.b.c` in the doc where this cast error occurred - * @param {Error} [reason] The original error that was thrown - * @api public - */ - -Mongoose.prototype.CastError = require('./error/cast'); - -/** - * The constructor used for schematype options - * - * @method SchemaTypeOptions - * @api public - */ - -Mongoose.prototype.SchemaTypeOptions = require('./options/SchemaTypeOptions'); - -/** - * The [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver Mongoose uses. - * - * @property mongo - * @api public - */ - -Mongoose.prototype.mongo = require('mongodb'); - -/** - * The [mquery](https://github.com/aheckmann/mquery) query builder Mongoose uses. - * - * @property mquery - * @api public - */ - -Mongoose.prototype.mquery = require('mquery'); - -/** - * Sanitizes query filters against [query selector injection attacks](https://thecodebarbarian.com/2014/09/04/defending-against-query-selector-injection-attacks.html) - * by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. - * - * ```javascript - * const obj = { username: 'val', pwd: { $ne: null } }; - * sanitizeFilter(obj); - * obj; // { username: 'val', pwd: { $eq: { $ne: null } } }); - * ``` - * - * @method sanitizeFilter - * @param {Object} filter - * @returns Object the sanitized object - * @api public - */ - -Mongoose.prototype.sanitizeFilter = sanitizeFilter; - -/** - * Tells `sanitizeFilter()` to skip the given object when filtering out potential [query selector injection attacks](https://thecodebarbarian.com/2014/09/04/defending-against-query-selector-injection-attacks.html). - * Use this method when you have a known query selector that you want to use. - * - * ```javascript - * const obj = { username: 'val', pwd: trusted({ $type: 'string', $eq: 'my secret' }) }; - * sanitizeFilter(obj); - * - * // Note that `sanitizeFilter()` did not add `$eq` around `$type`. - * obj; // { username: 'val', pwd: { $type: 'string', $eq: 'my secret' } }); - * ``` - * - * @method trusted - * @param {Object} obj - * @returns Object the passed in object - * @api public - */ - -Mongoose.prototype.trusted = trusted; - -/** - * Use this function in `pre()` middleware to skip calling the wrapped function. - * - * #### Example: - * - * schema.pre('save', function() { - * // Will skip executing `save()`, but will execute post hooks as if - * // `save()` had executed with the result `{ matchedCount: 0 }` - * return mongoose.skipMiddlewareFunction({ matchedCount: 0 }); - * }); - * - * @method skipMiddlewareFunction - * @param {any} result - * @api public - */ - -Mongoose.prototype.skipMiddlewareFunction = Kareem.skipWrappedFunction; - -/** - * Use this function in `post()` middleware to replace the result - * - * #### Example: - * - * schema.post('find', function(res) { - * // Normally you have to modify `res` in place. But with - * // `overwriteMiddlewarResult()`, you can make `find()` return a - * // completely different value. - * return mongoose.overwriteMiddlewareResult(res.filter(doc => !doc.isDeleted)); - * }); - * - * @method overwriteMiddlewareResult - * @param {any} result - * @api public - */ - -Mongoose.prototype.overwriteMiddlewareResult = Kareem.overwriteResult; - -/** - * The exports object is an instance of Mongoose. - * - * @api private - */ - -const mongoose = module.exports = exports = new Mongoose({ - [defaultMongooseSymbol]: true -}); +module.exports = mongoose; 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 5d9990d88e5..763b02bf6d0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -5,11 +5,12 @@ */ const Aggregate = require('./aggregate'); -const ChangeStream = require('./cursor/ChangeStream'); +const ChangeStream = require('./cursor/changeStream'); 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,10 +62,11 @@ 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'); +const minimize = require('./helpers/minimize'); const VERSION_WHERE = 1; const VERSION_INC = 2; @@ -341,7 +343,19 @@ Model.prototype.$__handleSave = function(options, callback) { } _applyCustomWhere(this, where); - this[modelCollectionSymbol].updateOne(where, delta[1], saveOptions).then( + + const update = delta[1]; + if (this.$__schema.options.minimize) { + minimize(update); + // minimize might leave us with an empty object, which would + // lead to MongoDB throwing a "Update document requires atomic operators" error + if (Object.keys(update).length === 0) { + handleEmptyUpdate.call(this); + return; + } + } + + this[modelCollectionSymbol].updateOne(where, update, saveOptions).then( ret => { ret.$where = where; callback(null, ret); @@ -353,6 +367,17 @@ Model.prototype.$__handleSave = function(options, callback) { } ); } else { + handleEmptyUpdate.call(this); + return; + } + + // store the modified paths before the document is reset + this.$__.modifiedPaths = this.modifiedPaths(); + this.$__reset(); + + _setIsNew(this, false); + + function handleEmptyUpdate() { const optionsWithCustomValues = Object.assign({}, options, saveOptions); const where = this.$__where(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; @@ -369,14 +394,7 @@ Model.prototype.$__handleSave = function(options, callback) { callback(null, { $where: where, matchedCount }); }) .catch(callback); - return; } - - // store the modified paths before the document is reset - this.$__.modifiedPaths = this.modifiedPaths(); - this.$__reset(); - - _setIsNew(this, false); }; /*! @@ -987,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'); @@ -1012,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; }; /** @@ -2261,34 +2271,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. @@ -2412,7 +2394,6 @@ Model.$where = function $where() { * @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.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 {Boolean} [options.overwrite=false] If set to `true`, Mongoose will convert this `findOneAndUpdate()` to a `findOneAndReplace()`. This option is deprecated and only supported for backwards compatiblity. * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document * @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 {Boolean} [options.new=false] if true, return the modified document rather than the original @@ -2421,7 +2402,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 @@ -2487,9 +2468,6 @@ Model.findOneAndUpdate = function(conditions, update, options) { * // is sent as * Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options) * - * This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`. - * To prevent this behaviour, see the `overwrite` option - * * #### Note: * * `findOneAndX` and `findByIdAndX` functions support limited validation. You can @@ -2510,11 +2488,10 @@ Model.findOneAndUpdate = function(conditions, update, options) { * @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.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 {Boolean} [options.overwrite=false] If set to `true`, Mongoose will convert this `findByIdAndUpdate()` to a `findByIdAndReplace()`. This option is deprecated and only supported for backwards compatiblity. * @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. @@ -2548,12 +2525,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 @@ -2575,7 +2546,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 @@ -2617,7 +2588,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/ */ @@ -2656,7 +2627,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. @@ -2683,104 +2654,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 @@ -2894,16 +2767,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; @@ -2915,13 +2786,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); + } - res = await p; + await toSave.$save(options); + + return toSave; + } catch (err) { + if (!firstError) { + firstError = err; + } + } + })); + if (firstError) { + throw firstError; + } } @@ -3849,7 +3743,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; }; @@ -4090,7 +3984,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 */ @@ -4149,8 +4043,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) { @@ -4166,20 +4071,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) { @@ -4195,7 +4087,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) { if (error) { reject(error); } else { - resolve(); + resolve(obj); } } } diff --git a/lib/mongoose.js b/lib/mongoose.js new file mode 100644 index 00000000000..64015066091 --- /dev/null +++ b/lib/mongoose.js @@ -0,0 +1,1269 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const Document = require('./document'); +const EventEmitter = require('events').EventEmitter; +const Kareem = require('kareem'); +const Schema = require('./schema'); +const SchemaType = require('./schemaType'); +const SchemaTypes = require('./schema/index'); +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'); +const applyPlugins = require('./helpers/schema/applyPlugins'); +const builtinPlugins = require('./plugins'); +const driver = require('./driver'); +const legacyPluralize = require('./helpers/pluralize'); +const utils = require('./utils'); +const pkg = require('../package.json'); +const cast = require('./cast'); + +const Aggregate = require('./aggregate'); +const trusted = require('./helpers/query/trusted').trusted; +const sanitizeFilter = require('./helpers/query/sanitizeFilter'); +const isBsonType = require('./helpers/isBsonType'); +const MongooseError = require('./error/mongooseError'); +const SetOptionError = require('./error/setOptionError'); + +const defaultMongooseSymbol = Symbol.for('mongoose:default'); + +require('./helpers/printJestWarning'); + +const objectIdHexRegexp = /^[0-9A-Fa-f]{24}$/; + +/** + * Mongoose constructor. + * + * The exports object of the `mongoose` module is an instance of this class. + * Most apps will only use this one instance. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * mongoose instanceof mongoose.Mongoose; // true + * + * // Create a new Mongoose instance with its own `connect()`, `set()`, `model()`, etc. + * const m = new mongoose.Mongoose(); + * + * @api public + * @param {Object} options see [`Mongoose#set()` docs](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.set()) + */ +function Mongoose(options) { + this.connections = []; + this.nextConnectionId = 0; + this.models = {}; + this.events = new EventEmitter(); + this.__driver = driver.get(); + // default global options + this.options = Object.assign({ + pluralization: true, + autoIndex: true, + autoCreate: true + }, options); + const createInitialConnection = utils.getOption('createInitialConnection', this.options); + if (createInitialConnection == null || createInitialConnection) { + const conn = this.createConnection(); // default connection + conn.models = this.models; + } + + if (this.options.pluralization) { + this._pluralize = legacyPluralize; + } + + // If a user creates their own Mongoose instance, give them a separate copy + // of the `Schema` constructor so they get separate custom types. (gh-6933) + if (!options || !options[defaultMongooseSymbol]) { + const _this = this; + this.Schema = function() { + this.base = _this; + return Schema.apply(this, arguments); + }; + this.Schema.prototype = Object.create(Schema.prototype); + + Object.assign(this.Schema, Schema); + this.Schema.base = this; + this.Schema.Types = Object.assign({}, Schema.Types); + } else { + // Hack to work around babel's strange behavior with + // `import mongoose, { Schema } from 'mongoose'`. Because `Schema` is not + // an own property of a Mongoose global, Schema will be undefined. See gh-5648 + for (const key of ['Schema', 'model']) { + this[key] = Mongoose.prototype[key]; + } + } + this.Schema.prototype.base = this; + + Object.defineProperty(this, 'plugins', { + configurable: false, + enumerable: true, + writable: false, + value: Object.values(builtinPlugins).map(plugin => ([plugin, { deduplicate: true }])) + }); +} + +Mongoose.prototype.cast = cast; +/** + * Expose connection states for user-land + * + * @memberOf Mongoose + * @property STATES + * @api public + */ +Mongoose.prototype.STATES = STATES; + +/** + * Expose connection states for user-land + * + * @memberOf Mongoose + * @property ConnectionStates + * @api public + */ +Mongoose.prototype.ConnectionStates = STATES; + +/** + * Object with `get()` and `set()` containing the underlying driver this Mongoose instance + * uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions + * like `find()`. + * + * @deprecated + * @memberOf Mongoose + * @property driver + * @api public + */ + +Mongoose.prototype.driver = driver; + +/** + * Overwrites the current driver used by this Mongoose instance. A driver is a + * Mongoose-specific interface that defines functions like `find()`. + * + * @memberOf Mongoose + * @method setDriver + * @api public + */ + +Mongoose.prototype.setDriver = function setDriver(driver) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (_mongoose.__driver === driver) { + return _mongoose; + } + + const openConnection = _mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected); + if (openConnection) { + const msg = 'Cannot modify Mongoose driver if a connection is already open. ' + + 'Call `mongoose.disconnect()` before modifying the driver'; + throw new MongooseError(msg); + } + _mongoose.__driver = driver; + + const Connection = driver.Connection; + _mongoose.connections = [new Connection(_mongoose)]; + _mongoose.connections[0].models = _mongoose.models; + + return _mongoose; +}; + +/** + * Sets mongoose options + * + * `key` can be used a object to set multiple options at once. + * If a error gets thrown for one option, other options will still be evaluated. + * + * #### Example: + * + * mongoose.set('test', value) // sets the 'test' option to `value` + * + * mongoose.set('debug', true) // enable logging collection methods + arguments to the console/file + * + * mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments + * + * mongoose.set({ debug: true, autoIndex: false }); // set multiple options at once + * + * Currently supported options are: + * - `allowDiskUse`: Set to `true` to set `allowDiskUse` to true to all aggregation operations by default. + * - `applyPluginsToChildSchemas`: `true` by default. Set to false to skip applying global plugins to child schemas + * - `applyPluginsToDiscriminators`: `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema. + * - `autoCreate`: Set to `true` to make Mongoose call [`Model.createCollection()`](https://mongoosejs.com/docs/api/model.html#Model.createCollection()) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist. + * - `autoIndex`: `true` by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance. + * - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models + * - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds). + * - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model. + * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. + * - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis. + * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable) which means you can update the `createdAt` + * - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query + * - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter. + * - `overwriteModels`: Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`. + * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information. + * - `runValidators`: `false` by default. Set to true to enable [update validators](https://mongoosejs.com/docs/validation.html#update-validators) for all validators by default. + * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.sanitizeFilter()) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. + * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. + * - `strict`: `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. + * - `strictQuery`: `false` by default. May be `false`, `true`, or `'throw'`. Sets the default [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas. + * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toJSON()), for determining how Mongoose documents get serialized by `JSON.stringify()` + * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) + * + * @param {String|Object} key The name of the option or a object of multiple key-value pairs + * @param {String|Function|Boolean} value The value of the option, unused if "key" is a object + * @returns {Mongoose} The used Mongoose instnace + * @api public + */ + +Mongoose.prototype.set = function(key, value) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (arguments.length === 1 && typeof key !== 'object') { + if (VALID_OPTIONS.indexOf(key) === -1) { + const error = new SetOptionError(); + error.addError(key, new SetOptionError.SetOptionInnerError(key)); + throw error; + } + + return _mongoose.options[key]; + } + + let options = {}; + + if (arguments.length === 2) { + options = { [key]: value }; + } + + if (arguments.length === 1 && typeof key === 'object') { + options = key; + } + + // array for errors to collect all errors for all key-value pairs, like ".validate" + let error = undefined; + + for (const [optionKey, optionValue] of Object.entries(options)) { + if (VALID_OPTIONS.indexOf(optionKey) === -1) { + if (!error) { + error = new SetOptionError(); + } + error.addError(optionKey, new SetOptionError.SetOptionInnerError(optionKey)); + continue; + } + + _mongoose.options[optionKey] = optionValue; + + if (optionKey === 'objectIdGetter') { + if (optionValue) { + Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', { + enumerable: false, + configurable: true, + get: function() { + return this; + } + }); + } else { + delete mongoose.Types.ObjectId.prototype._id; + } + } + } + + if (error) { + throw error; + } + + return _mongoose; +}; + +/** + * Gets mongoose options + * + * #### Example: + * + * mongoose.get('test') // returns the 'test' value + * + * @param {String} key + * @method get + * @api public + */ + +Mongoose.prototype.get = Mongoose.prototype.set; + +/** + * Creates a Connection instance. + * + * Each `connection` instance maps to a single database. This method is helpful when managing multiple db connections. + * + * + * _Options passed take precedence over options included in connection strings._ + * + * #### Example: + * + * // with mongodb:// URI + * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database'); + * + * // and options + * const opts = { db: { native_parser: true }} + * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', opts); + * + * // replica sets + * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database'); + * + * // and options + * const opts = { replset: { strategy: 'ping', rs_name: 'testSet' }} + * db = mongoose.createConnection('mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/database', opts); + * + * // initialize now, connect later + * db = mongoose.createConnection(); + * db.openUri('127.0.0.1', 'database', port, [opts]); + * + * @param {String} uri mongodb URI to connect to + * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below. + * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. + * @param {String} [options.dbName] The name of the database you want to use. If not provided, Mongoose uses the database name from connection string. + * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. + * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. + * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. + * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary). + * @param {Number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. Defaults to 0, which means Node.js will not time out the socket due to inactivity. A socket may be inactive because of either no activity or a long-running operation. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. + * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. + * @return {Connection} the created Connection object. Connections are not thenable, so you can't do `await mongoose.createConnection()`. To await use `mongoose.createConnection(uri).asPromise()` instead. + * @api public + */ + +Mongoose.prototype.createConnection = function(uri, options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + const Connection = _mongoose.__driver.Connection; + const conn = new Connection(_mongoose); + _mongoose.connections.push(conn); + _mongoose.nextConnectionId++; + _mongoose.events.emit('createConnection', conn); + + if (arguments.length > 0) { + conn.openUri(uri, { ...options, _fireAndForget: true }); + } + + return conn; +}; + +/** + * Opens the default mongoose connection. + * + * #### Example: + * + * mongoose.connect('mongodb://user:pass@127.0.0.1:port/database'); + * + * // replica sets + * const uri = 'mongodb://user:pass@127.0.0.1:port,anotherhost:port,yetanother:port/mydatabase'; + * mongoose.connect(uri); + * + * // with options + * mongoose.connect(uri, options); + * + * // optional callback that gets fired when initial connection completed + * const uri = 'mongodb://nonexistent.domain:27000'; + * mongoose.connect(uri, function(error) { + * // if error is truthy, the initial connection failed. + * }) + * + * @param {String} uri mongodb URI to connect to + * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below. + * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](https://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. + * @param {Number} [options.bufferTimeoutMS=10000] Mongoose specific option. If `bufferCommands` is true, Mongoose will throw an error after `bufferTimeoutMS` if the operation is still buffered. + * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. + * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. + * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. + * @param {Number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. + * @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds). + * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. + * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. + * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html#promiseLibrary). + * @param {Number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. `socketTimeoutMS` defaults to 0, which means Node.js will not time out the socket due to inactivity. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. + * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. + * @param {Boolean} [options.autoCreate=false] Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection. + * @param {Function} [callback] + * @see Mongoose#createConnection https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection() + * @api public + * @return {Promise} resolves to `this` if connection succeeded + */ + +Mongoose.prototype.connect = async function connect(uri, options) { + if (typeof options === 'function' || (arguments.length >= 3 && typeof arguments[2] === 'function')) { + throw new MongooseError('Mongoose.prototype.connect() no longer accepts a callback'); + } + + const _mongoose = this instanceof Mongoose ? this : mongoose; + const conn = _mongoose.connection; + + return conn.openUri(uri, options).then(() => _mongoose); +}; + +/** + * Runs `.close()` on all connections in parallel. + * + * @return {Promise} resolves when all connections are closed, or rejects with the first error that occurred. + * @api public + */ + +Mongoose.prototype.disconnect = async function disconnect() { + if (arguments.length >= 1 && typeof arguments[0] === 'function') { + throw new MongooseError('Mongoose.prototype.disconnect() no longer accepts a callback'); + } + + const _mongoose = this instanceof Mongoose ? this : mongoose; + + const remaining = _mongoose.connections.length; + if (remaining <= 0) { + return; + } + await Promise.all(_mongoose.connections.map(conn => conn.close())); +}; + +/** + * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions) + * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/), + * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). + * + * Calling `mongoose.startSession()` is equivalent to calling `mongoose.connection.startSession()`. + * Sessions are scoped to a connection, so calling `mongoose.startSession()` + * starts a session on the [default mongoose connection](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connection). + * + * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession) + * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency + * @param {Function} [callback] + * @return {Promise} promise that resolves to a MongoDB driver `ClientSession` + * @api public + */ + +Mongoose.prototype.startSession = function() { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + return _mongoose.connection.startSession.apply(_mongoose.connection, arguments); +}; + +/** + * Getter/setter around function for pluralizing collection names. + * + * @param {Function|null} [fn] overwrites the function used to pluralize collection names + * @return {Function|null} the current function used to pluralize collection names, defaults to the legacy function from `mongoose-legacy-pluralize`. + * @api public + */ + +Mongoose.prototype.pluralize = function(fn) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (arguments.length > 0) { + _mongoose._pluralize = fn; + } + return _mongoose._pluralize; +}; + +/** + * Defines a model or retrieves it. + * + * Models defined on the `mongoose` instance are available to all connection + * created by the same `mongoose` instance. + * + * If you call `mongoose.model()` with twice the same name but a different schema, + * you will get an `OverwriteModelError`. If you call `mongoose.model()` with + * the same name and same schema, you'll get the same schema back. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * + * // define an Actor model with this mongoose instance + * const schema = new Schema({ name: String }); + * mongoose.model('Actor', schema); + * + * // create a new connection + * const conn = mongoose.createConnection(..); + * + * // create Actor model + * const Actor = conn.model('Actor', schema); + * conn.model('Actor') === Actor; // true + * conn.model('Actor', schema) === Actor; // true, same schema + * conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name + * + * // This throws an `OverwriteModelError` because the schema is different. + * conn.model('Actor', new Schema({ name: String })); + * + * _When no `collection` argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use `mongoose.pluralize()`, or set your schemas collection name option._ + * + * #### Example: + * + * const schema = new Schema({ name: String }, { collection: 'actor' }); + * + * // or + * + * schema.set('collection', 'actor'); + * + * // or + * + * const collectionName = 'actor' + * const M = mongoose.model('Actor', schema, collectionName) + * + * @param {String|Function} name model name or class extending Model + * @param {Schema} [schema] the schema to use. + * @param {String} [collection] name (optional, inferred from model name) + * @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist. + * @api public + */ + +Mongoose.prototype.model = function(name, schema, collection, options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (typeof schema === 'string') { + collection = schema; + schema = false; + } + + if (arguments.length === 1) { + const model = _mongoose.models[name]; + if (!model) { + throw new MongooseError.MissingSchemaError(name); + } + return model; + } + + if (utils.isObject(schema) && !(schema instanceof Schema)) { + schema = new Schema(schema); + } + if (schema && !(schema instanceof Schema)) { + throw new Error('The 2nd parameter to `mongoose.model()` should be a ' + + 'schema or a POJO'); + } + + // handle internal options from connection.model() + options = options || {}; + + const originalSchema = schema; + if (schema) { + if (_mongoose.get('cloneSchemas')) { + schema = schema.clone(); + } + _mongoose._applyPlugins(schema); + } + + // connection.model() may be passing a different schema for + // an existing model name. in this case don't read from cache. + const overwriteModels = _mongoose.options.hasOwnProperty('overwriteModels') ? + _mongoose.options.overwriteModels : + options.overwriteModels; + if (_mongoose.models.hasOwnProperty(name) && options.cache !== false && overwriteModels !== true) { + if (originalSchema && + originalSchema.instanceOfSchema && + originalSchema !== _mongoose.models[name].schema) { + throw new _mongoose.Error.OverwriteModelError(name); + } + if (collection && collection !== _mongoose.models[name].collection.name) { + // subclass current model with alternate collection + const model = _mongoose.models[name]; + schema = model.prototype.schema; + const sub = model.__subclass(_mongoose.connection, schema, collection); + // do not cache the sub model + return sub; + } + return _mongoose.models[name]; + } + if (schema == null) { + throw new _mongoose.Error.MissingSchemaError(name); + } + + const model = _mongoose._model(name, schema, collection, options); + _mongoose.connection.models[name] = model; + _mongoose.models[name] = model; + + return model; +}; + +/*! + * ignore + */ + +Mongoose.prototype._model = function(name, schema, collection, options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + let model; + if (typeof name === 'function') { + model = name; + name = model.name; + if (!(model.prototype instanceof Model)) { + throw new _mongoose.Error('The provided class ' + name + ' must extend Model'); + } + } + + if (schema) { + if (_mongoose.get('cloneSchemas')) { + schema = schema.clone(); + } + _mongoose._applyPlugins(schema); + } + + // Apply relevant "global" options to the schema + if (schema == null || !('pluralization' in schema.options)) { + schema.options.pluralization = _mongoose.options.pluralization; + } + + if (!collection) { + collection = schema.get('collection') || + utils.toCollectionName(name, _mongoose.pluralize()); + } + + const connection = options.connection || _mongoose.connection; + model = _mongoose.Model.compile(model || name, schema, collection, connection, _mongoose); + // Errors handled internally, so safe to ignore error + model.init().catch(function $modelInitNoop() {}); + + connection.emit('model', model); + + if (schema._applyDiscriminators != null) { + for (const disc of schema._applyDiscriminators.keys()) { + model.discriminator(disc, schema._applyDiscriminators.get(disc)); + } + } + + return model; +}; + +/** + * Removes the model named `name` from the default connection, if it exists. + * You can use this function to clean up any models you created in your tests to + * prevent OverwriteModelErrors. + * + * Equivalent to `mongoose.connection.deleteModel(name)`. + * + * #### Example: + * + * mongoose.model('User', new Schema({ name: String })); + * console.log(mongoose.model('User')); // Model object + * mongoose.deleteModel('User'); + * console.log(mongoose.model('User')); // undefined + * + * // Usually useful in a Mocha `afterEach()` hook + * afterEach(function() { + * mongoose.deleteModel(/.+/); // Delete every model + * }); + * + * @api public + * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp. + * @return {Mongoose} this + */ + +Mongoose.prototype.deleteModel = function(name) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + _mongoose.connection.deleteModel(name); + delete _mongoose.models[name]; + return _mongoose; +}; + +/** + * Returns an array of model names created on this instance of Mongoose. + * + * #### Note: + * + * _Does not include names of models created using `connection.model()`._ + * + * @api public + * @return {Array} + */ + +Mongoose.prototype.modelNames = function() { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + const names = Object.keys(_mongoose.models); + return names; +}; + +/** + * Applies global plugins to `schema`. + * + * @param {Schema} schema + * @api private + */ + +Mongoose.prototype._applyPlugins = function(schema, options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + options = options || {}; + options.applyPluginsToDiscriminators = _mongoose.options && _mongoose.options.applyPluginsToDiscriminators || false; + options.applyPluginsToChildSchemas = typeof (_mongoose.options && _mongoose.options.applyPluginsToChildSchemas) === 'boolean' ? + _mongoose.options.applyPluginsToChildSchemas : + true; + applyPlugins(schema, _mongoose.plugins, options, '$globalPluginsApplied'); +}; + +/** + * Declares a global plugin executed on all Schemas. + * + * Equivalent to calling `.plugin(fn)` on each Schema you create. + * + * @param {Function} fn plugin callback + * @param {Object} [opts] optional options + * @return {Mongoose} this + * @see plugins https://mongoosejs.com/docs/plugins.html + * @api public + */ + +Mongoose.prototype.plugin = function(fn, opts) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + _mongoose.plugins.push([fn, opts]); + return _mongoose; +}; + +/** + * The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.connections). + * + * #### Example: + * + * const mongoose = require('mongoose'); + * mongoose.connect(...); + * mongoose.connection.on('error', cb); + * + * This is the connection used by default for every model created using [mongoose.model](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()). + * + * To create a new connection, use [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()). + * + * @memberOf Mongoose + * @instance + * @property {Connection} connection + * @api public + */ + +Mongoose.prototype.__defineGetter__('connection', function() { + return this.connections[0]; +}); + +Mongoose.prototype.__defineSetter__('connection', function(v) { + if (v instanceof this.__driver.Connection) { + this.connections[0] = v; + this.models = v.models; + } +}); + +/** + * An array containing all [connections](connection.html) associated with this + * Mongoose instance. By default, there is 1 connection. Calling + * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) adds a connection + * to this array. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * mongoose.connections.length; // 1, just the default connection + * mongoose.connections[0] === mongoose.connection; // true + * + * mongoose.createConnection('mongodb://127.0.0.1:27017/test'); + * mongoose.connections.length; // 2 + * + * @memberOf Mongoose + * @instance + * @property {Array} connections + * @api public + */ + +Mongoose.prototype.connections; + +/** + * An integer containing the value of the next connection id. Calling + * [`createConnection()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.createConnection()) increments + * this value. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * mongoose.createConnection(); // id `0`, `nextConnectionId` becomes `1` + * mongoose.createConnection(); // id `1`, `nextConnectionId` becomes `2` + * mongoose.connections[0].destroy() // Removes connection with id `0` + * mongoose.createConnection(); // id `2`, `nextConnectionId` becomes `3` + * + * @memberOf Mongoose + * @instance + * @property {Number} nextConnectionId + * @api private + */ + +Mongoose.prototype.nextConnectionId; + +/** + * The Mongoose Aggregate constructor + * + * @method Aggregate + * @api public + */ + +Mongoose.prototype.Aggregate = Aggregate; + +/** + * The Mongoose Collection constructor + * + * @memberOf Mongoose + * @instance + * @method Collection + * @api public + */ + +Object.defineProperty(Mongoose.prototype, 'Collection', { + get: function() { + return this.__driver.Collection; + }, + set: function(Collection) { + this.__driver.Collection = Collection; + } +}); + +/** + * The Mongoose [Connection](https://mongoosejs.com/docs/api/connection.html#Connection()) constructor + * + * @memberOf Mongoose + * @instance + * @method Connection + * @api public + */ + +Object.defineProperty(Mongoose.prototype, 'Connection', { + get: function() { + return this.__driver.Connection; + }, + set: function(Connection) { + if (Connection === this.__driver.Connection) { + return; + } + + this.__driver.Connection = Connection; + } +}); + +/** + * The Mongoose version + * + * #### Example: + * + * console.log(mongoose.version); // '5.x.x' + * + * @property version + * @api public + */ + +Mongoose.prototype.version = pkg.version; + +/** + * The Mongoose constructor + * + * The exports of the mongoose module is an instance of this class. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * const mongoose2 = new mongoose.Mongoose(); + * + * @method Mongoose + * @api public + */ + +Mongoose.prototype.Mongoose = Mongoose; + +/** + * The Mongoose [Schema](https://mongoosejs.com/docs/api/schema.html#Schema()) constructor + * + * #### Example: + * + * const mongoose = require('mongoose'); + * const Schema = mongoose.Schema; + * const CatSchema = new Schema(..); + * + * @method Schema + * @api public + */ + +Mongoose.prototype.Schema = Schema; + +/** + * The Mongoose [SchemaType](https://mongoosejs.com/docs/api/schematype.html#SchemaType()) constructor + * + * @method SchemaType + * @api public + */ + +Mongoose.prototype.SchemaType = SchemaType; + +/** + * The various Mongoose SchemaTypes. + * + * #### Note: + * + * _Alias of mongoose.Schema.Types for backwards compatibility._ + * + * @property SchemaTypes + * @see Schema.SchemaTypes https://mongoosejs.com/docs/schematypes.html + * @api public + */ + +Mongoose.prototype.SchemaTypes = Schema.Types; + +/** + * The Mongoose [VirtualType](https://mongoosejs.com/docs/api/virtualtype.html#VirtualType()) constructor + * + * @method VirtualType + * @api public + */ + +Mongoose.prototype.VirtualType = VirtualType; + +/** + * The various Mongoose Types. + * + * #### Example: + * + * const mongoose = require('mongoose'); + * const array = mongoose.Types.Array; + * + * #### Types: + * + * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays) + * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers) + * - [Embedded](https://mongoosejs.com/docs/schematypes.html#schemas) + * - [DocumentArray](https://mongoosejs.com/docs/api/documentarraypath.html) + * - [Decimal128](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.Decimal128) + * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) + * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) + * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) + * + * Using this exposed access to the `ObjectId` type, we can construct ids on demand. + * + * const ObjectId = mongoose.Types.ObjectId; + * const id1 = new ObjectId; + * + * @property Types + * @api public + */ + +Mongoose.prototype.Types = Types; + +/** + * The Mongoose [Query](https://mongoosejs.com/docs/api/query.html#Query()) constructor. + * + * @method Query + * @api public + */ + +Mongoose.prototype.Query = Query; + +/** + * The Mongoose [Model](https://mongoosejs.com/docs/api/model.html#Model()) constructor. + * + * @method Model + * @api public + */ + +Mongoose.prototype.Model = Model; + +/** + * The Mongoose [Document](https://mongoosejs.com/docs/api/document.html#Document()) constructor. + * + * @method Document + * @api public + */ + +Mongoose.prototype.Document = Document; + +/** + * The Mongoose DocumentProvider constructor. Mongoose users should not have to + * use this directly + * + * @method DocumentProvider + * @api public + */ + +Mongoose.prototype.DocumentProvider = require('./documentProvider'); + +/** + * The Mongoose ObjectId [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for + * declaring paths in your schema that should be + * [MongoDB ObjectIds](https://www.mongodb.com/docs/manual/reference/method/ObjectId/). + * Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId` + * instead. + * + * #### Example: + * + * const childSchema = new Schema({ parentId: mongoose.ObjectId }); + * + * @property ObjectId + * @api public + */ + +Mongoose.prototype.ObjectId = SchemaTypes.ObjectId; + +/** + * Returns true if Mongoose can cast the given value to an ObjectId, or + * false otherwise. + * + * #### Example: + * + * mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true + * mongoose.isValidObjectId('0123456789ab'); // true + * mongoose.isValidObjectId(6); // true + * mongoose.isValidObjectId(new User({ name: 'test' })); // true + * + * mongoose.isValidObjectId({ test: 42 }); // false + * + * @method isValidObjectId + * @param {Any} v + * @returns {boolean} true if `v` is something Mongoose can coerce to an ObjectId + * @api public + */ + +Mongoose.prototype.isValidObjectId = function(v) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + return _mongoose.Types.ObjectId.isValid(v); +}; + +/** + * Returns true if the given value is a Mongoose ObjectId (using `instanceof`) or if the + * given value is a 24 character hex string, which is the most commonly used string representation + * of an ObjectId. + * + * This function is similar to `isValidObjectId()`, but considerably more strict, because + * `isValidObjectId()` will return `true` for _any_ value that Mongoose can convert to an + * ObjectId. That includes Mongoose documents, any string of length 12, and any number. + * `isObjectIdOrHexString()` returns true only for `ObjectId` instances or 24 character hex + * strings, and will return false for numbers, documents, and strings of length 12. + * + * #### Example: + * + * mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true + * mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true + * + * mongoose.isObjectIdOrHexString('0123456789ab'); // false + * mongoose.isObjectIdOrHexString(6); // false + * mongoose.isObjectIdOrHexString(new User({ name: 'test' })); // false + * mongoose.isObjectIdOrHexString({ test: 42 }); // false + * + * @method isObjectIdOrHexString + * @param {Any} v + * @returns {boolean} true if `v` is an ObjectId instance _or_ a 24 char hex string + * @api public + */ + +Mongoose.prototype.isObjectIdOrHexString = function(v) { + return isBsonType(v, 'ObjectId') || (typeof v === 'string' && objectIdHexRegexp.test(v)); +}; + +/** + * + * Syncs all the indexes for the models registered with this connection. + * + * @param {Object} options + * @param {Boolean} options.continueOnError `false` by default. If set to `true`, mongoose will not throw an error if one model syncing failed, and will return an object where the keys are the names of the models, and the values are the results/errors for each model. + * @return {Promise} Returns a Promise, when the Promise resolves the value is a list of the dropped indexes. + */ +Mongoose.prototype.syncIndexes = function(options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + return _mongoose.connection.syncIndexes(options); +}; + +/** + * The Mongoose Decimal128 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for + * declaring paths in your schema that should be + * [128-bit decimal floating points](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html). + * Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128` + * instead. + * + * #### Example: + * + * const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 }); + * + * @property Decimal128 + * @api public + */ + +Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; + +/** + * The Mongoose Mixed [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for + * declaring paths in your schema that Mongoose's change tracking, casting, + * and validation should ignore. + * + * #### Example: + * + * const schema = new Schema({ arbitrary: mongoose.Mixed }); + * + * @property Mixed + * @api public + */ + +Mongoose.prototype.Mixed = SchemaTypes.Mixed; + +/** + * The Mongoose Date [SchemaType](https://mongoosejs.com/docs/schematypes.html). + * + * #### Example: + * + * const schema = new Schema({ test: Date }); + * schema.path('test') instanceof mongoose.Date; // true + * + * @property Date + * @api public + */ + +Mongoose.prototype.Date = SchemaTypes.Date; + +/** + * The Mongoose Number [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for + * declaring paths in your schema that Mongoose should cast to numbers. + * + * #### Example: + * + * const schema = new Schema({ num: mongoose.Number }); + * // Equivalent to: + * const schema = new Schema({ num: 'number' }); + * + * @property Number + * @api public + */ + +Mongoose.prototype.Number = SchemaTypes.Number; + +/** + * The [MongooseError](https://mongoosejs.com/docs/api/error.html#Error()) constructor. + * + * @method Error + * @api public + */ + +Mongoose.prototype.Error = require('./error/index'); +Mongoose.prototype.MongooseError = require('./error/mongooseError'); + +/** + * Mongoose uses this function to get the current time when setting + * [timestamps](https://mongoosejs.com/docs/guide.html#timestamps). You may stub out this function + * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. + * + * @method now + * @returns Date the current time + * @api public + */ + +Mongoose.prototype.now = function now() { return new Date(); }; + +/** + * The Mongoose CastError constructor + * + * @method CastError + * @param {String} type The name of the type + * @param {Any} value The value that failed to cast + * @param {String} path The path `a.b.c` in the doc where this cast error occurred + * @param {Error} [reason] The original error that was thrown + * @api public + */ + +Mongoose.prototype.CastError = require('./error/cast'); + +/** + * The constructor used for schematype options + * + * @method SchemaTypeOptions + * @api public + */ + +Mongoose.prototype.SchemaTypeOptions = require('./options/schemaTypeOptions'); + +/** + * The [mquery](https://github.com/aheckmann/mquery) query builder Mongoose uses. + * + * @property mquery + * @api public + */ + +Mongoose.prototype.mquery = require('mquery'); + +/** + * Sanitizes query filters against [query selector injection attacks](https://thecodebarbarian.com/2014/09/04/defending-against-query-selector-injection-attacks.html) + * by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`. + * + * ```javascript + * const obj = { username: 'val', pwd: { $ne: null } }; + * sanitizeFilter(obj); + * obj; // { username: 'val', pwd: { $eq: { $ne: null } } }); + * ``` + * + * @method sanitizeFilter + * @param {Object} filter + * @returns Object the sanitized object + * @api public + */ + +Mongoose.prototype.sanitizeFilter = sanitizeFilter; + +/** + * Tells `sanitizeFilter()` to skip the given object when filtering out potential [query selector injection attacks](https://thecodebarbarian.com/2014/09/04/defending-against-query-selector-injection-attacks.html). + * Use this method when you have a known query selector that you want to use. + * + * ```javascript + * const obj = { username: 'val', pwd: trusted({ $type: 'string', $eq: 'my secret' }) }; + * sanitizeFilter(obj); + * + * // Note that `sanitizeFilter()` did not add `$eq` around `$type`. + * obj; // { username: 'val', pwd: { $type: 'string', $eq: 'my secret' } }); + * ``` + * + * @method trusted + * @param {Object} obj + * @returns Object the passed in object + * @api public + */ + +Mongoose.prototype.trusted = trusted; + +/** + * Use this function in `pre()` middleware to skip calling the wrapped function. + * + * #### Example: + * + * schema.pre('save', function() { + * // Will skip executing `save()`, but will execute post hooks as if + * // `save()` had executed with the result `{ matchedCount: 0 }` + * return mongoose.skipMiddlewareFunction({ matchedCount: 0 }); + * }); + * + * @method skipMiddlewareFunction + * @param {any} result + * @api public + */ + +Mongoose.prototype.skipMiddlewareFunction = Kareem.skipWrappedFunction; + +/** + * Use this function in `post()` middleware to replace the result + * + * #### Example: + * + * schema.post('find', function(res) { + * // Normally you have to modify `res` in place. But with + * // `overwriteMiddlewarResult()`, you can make `find()` return a + * // completely different value. + * return mongoose.overwriteMiddlewareResult(res.filter(doc => !doc.isDeleted)); + * }); + * + * @method overwriteMiddlewareResult + * @param {any} result + * @api public + */ + +Mongoose.prototype.overwriteMiddlewareResult = Kareem.overwriteResult; + +/** + * The exports object is an instance of Mongoose. + * + * @api private + */ + +const mongoose = module.exports = exports = new Mongoose({ + [defaultMongooseSymbol]: true +}); diff --git a/lib/options/PopulateOptions.js b/lib/options/populateOptions.js similarity index 100% rename from lib/options/PopulateOptions.js rename to lib/options/populateOptions.js diff --git a/lib/options/SchemaArrayOptions.js b/lib/options/schemaArrayOptions.js similarity index 97% rename from lib/options/SchemaArrayOptions.js rename to lib/options/schemaArrayOptions.js index 54ad4f09058..01f4fb78c64 100644 --- a/lib/options/SchemaArrayOptions.js +++ b/lib/options/schemaArrayOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on an Array schematype. diff --git a/lib/options/SchemaBufferOptions.js b/lib/options/schemaBufferOptions.js similarity index 92% rename from lib/options/SchemaBufferOptions.js rename to lib/options/schemaBufferOptions.js index 377e3566a58..bfc12cb4590 100644 --- a/lib/options/SchemaBufferOptions.js +++ b/lib/options/schemaBufferOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a Buffer schematype. diff --git a/lib/options/SchemaDateOptions.js b/lib/options/schemaDateOptions.js similarity index 96% rename from lib/options/SchemaDateOptions.js rename to lib/options/schemaDateOptions.js index c7d3d4e1044..a4ef62bb5fe 100644 --- a/lib/options/SchemaDateOptions.js +++ b/lib/options/schemaDateOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a Date schematype. diff --git a/lib/options/SchemaDocumentArrayOptions.js b/lib/options/schemaDocumentArrayOptions.js similarity index 96% rename from lib/options/SchemaDocumentArrayOptions.js rename to lib/options/schemaDocumentArrayOptions.js index b826b87877b..ae5b07b3c4b 100644 --- a/lib/options/SchemaDocumentArrayOptions.js +++ b/lib/options/schemaDocumentArrayOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on an Document Array schematype. diff --git a/lib/options/SchemaMapOptions.js b/lib/options/schemaMapOptions.js similarity index 94% rename from lib/options/SchemaMapOptions.js rename to lib/options/schemaMapOptions.js index bbabaa0700d..d6de6a4c74a 100644 --- a/lib/options/SchemaMapOptions.js +++ b/lib/options/schemaMapOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a Map schematype. diff --git a/lib/options/SchemaNumberOptions.js b/lib/options/schemaNumberOptions.js similarity index 97% rename from lib/options/SchemaNumberOptions.js rename to lib/options/schemaNumberOptions.js index 126e4244bac..62a1b51a64e 100644 --- a/lib/options/SchemaNumberOptions.js +++ b/lib/options/schemaNumberOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a Number schematype. diff --git a/lib/options/SchemaObjectIdOptions.js b/lib/options/schemaObjectIdOptions.js similarity index 95% rename from lib/options/SchemaObjectIdOptions.js rename to lib/options/schemaObjectIdOptions.js index 81c7bce7fbf..2029c9de578 100644 --- a/lib/options/SchemaObjectIdOptions.js +++ b/lib/options/schemaObjectIdOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on an ObjectId schematype. diff --git a/lib/options/SchemaStringOptions.js b/lib/options/schemaStringOptions.js similarity index 98% rename from lib/options/SchemaStringOptions.js rename to lib/options/schemaStringOptions.js index 69fe174070b..c5bd8ca69ee 100644 --- a/lib/options/SchemaStringOptions.js +++ b/lib/options/schemaStringOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a string schematype. diff --git a/lib/options/SchemaSubdocumentOptions.js b/lib/options/schemaSubdocumentOptions.js similarity index 94% rename from lib/options/SchemaSubdocumentOptions.js rename to lib/options/schemaSubdocumentOptions.js index 6782120350a..07c906726f4 100644 --- a/lib/options/SchemaSubdocumentOptions.js +++ b/lib/options/schemaSubdocumentOptions.js @@ -1,6 +1,6 @@ 'use strict'; -const SchemaTypeOptions = require('./SchemaTypeOptions'); +const SchemaTypeOptions = require('./schemaTypeOptions'); /** * The options defined on a single nested schematype. diff --git a/lib/options/SchemaTypeOptions.js b/lib/options/schemaTypeOptions.js similarity index 100% rename from lib/options/SchemaTypeOptions.js rename to lib/options/schemaTypeOptions.js diff --git a/lib/options/VirtualOptions.js b/lib/options/virtualOptions.js similarity index 100% rename from lib/options/VirtualOptions.js rename to lib/options/virtualOptions.js 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 30f22255d95..ff922c77577 100644 --- a/lib/query.js +++ b/lib/query.js @@ -9,7 +9,7 @@ const DocumentNotFoundError = require('./error/notFound'); const Kareem = require('kareem'); const MongooseError = require('./error/mongooseError'); const ObjectParameterError = require('./error/objectParameter'); -const QueryCursor = require('./cursor/QueryCursor'); +const QueryCursor = require('./cursor/queryCursor'); const ValidationError = require('./error/validation'); const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption'); const handleReadPreferenceAliases = require('./helpers/query/handleReadPreferenceAliases'); @@ -21,8 +21,7 @@ const castUpdate = require('./helpers/query/castUpdate'); 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 +36,7 @@ const sanitizeFilter = require('./helpers/query/sanitizeFilter'); const sanitizeProjection = require('./helpers/query/sanitizeProjection'); const selectPopulatedFields = require('./helpers/query/selectPopulatedFields'); const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert'); +const specialProperties = require('./helpers/specialProperties'); const updateValidators = require('./helpers/updateValidators'); const util = require('util'); const utils = require('./utils'); @@ -1218,7 +1218,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 +1548,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: * @@ -1619,10 +1618,6 @@ Query.prototype.setOptions = function(options, overwrite) { this._mongooseOptions.sanitizeFilter = options.sanitizeFilter; delete options.sanitizeFilter; } - if ('overwrite' in options) { - this._mongooseOptions.overwrite = options.overwrite; - delete options.overwrite; - } if ('timestamps' in options) { this._mongooseOptions.timestamps = options.timestamps; delete options.timestamps; @@ -1636,10 +1631,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,24 +1665,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: true` as a replacement for `rawResult: true`.' -); - -/*! - * ignore - */ - -const printOverwriteDeprecationWarning = util.deprecate( - function printOverwriteDeprecationWarning() {}, - 'The `overwrite` option for `findOneAndUpdate()` is deprecated. use `findOneAndReplace()` instead.' -); - /** * Sets the [`explain` option](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/), * which makes this query return detailed execution stats instead of the actual @@ -1911,11 +1884,6 @@ Query.prototype._updateForExec = function() { while (i--) { const op = ops[i]; - if (this._mongooseOptions.overwrite) { - ret[op] = update[op]; - continue; - } - if ('$' !== op[0]) { // fix up $set sugar if (!ret.$set) { @@ -2282,7 +2250,7 @@ Query.prototype._find = async function _find() { const filter = this._conditions; const fields = options.projection; - const cursor = await this._collection.collection.find(filter, options); + const cursor = await this.mongooseCollection.find(filter, options); if (options.explain) { return cursor.explain(); } @@ -2349,8 +2317,6 @@ Query.prototype.find = function(conditions) { this.error(new ObjectParameterError(conditions, 'filter', 'find')); } - Query.base.find.call(this); - return this; }; @@ -2456,7 +2422,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 +2431,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; @@ -2531,7 +2497,7 @@ Query.prototype._findOne = async function _findOne() { this._applyTranslateAliases(options); // don't pass in the conditions because we already merged them in - const doc = await this._collection.collection.findOne(this._conditions, options); + const doc = await this.mongooseCollection.findOne(this._conditions, options); return new Promise((resolve, reject) => { this._completeOne(doc, null, _wrapThunkCallback(this, (err, res) => { if (err) { @@ -2598,40 +2564,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 @@ -2660,7 +2595,7 @@ Query.prototype._countDocuments = async function _countDocuments() { const conds = this._conditions; - return this._collection.collection.countDocuments(conds, options); + return this.mongooseCollection.countDocuments(conds, options); }; /*! @@ -2705,51 +2640,7 @@ Query.prototype._estimatedDocumentCount = async function _estimatedDocumentCount const options = this._optionsForExec(); - 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; + return this.mongooseCollection.estimatedDocumentCount(options); }; /** @@ -2870,7 +2761,7 @@ Query.prototype.__distinct = async function __distinct() { const options = this._optionsForExec(); this._applyTranslateAliases(options); - return this._collection.collection. + return this.mongooseCollection. distinct(this._distinct, this._conditions, options); }; @@ -2952,9 +2843,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 +2957,6 @@ Query.prototype.deleteOne = function deleteOne(filter, options) { this.error(new ObjectParameterError(filter, 'filter', 'deleteOne')); } - Query.base.deleteOne.call(this); - return this; }; @@ -3027,7 +2979,7 @@ Query.prototype._deleteOne = async function _deleteOne() { const options = this._optionsForExec(); this._applyTranslateAliases(options); - return this._collection.collection.deleteOne(this._conditions, options); + return this.mongooseCollection.deleteOne(this._conditions, options); }; /** @@ -3078,8 +3030,6 @@ Query.prototype.deleteMany = function(filter, options) { this.error(new ObjectParameterError(filter, 'filter', 'deleteMany')); } - Query.base.deleteMany.call(this); - return this; }; @@ -3103,7 +3053,7 @@ Query.prototype._deleteMany = async function _deleteMany() { const options = this._optionsForExec(); this._applyTranslateAliases(options); - return this._collection.collection.deleteMany(this._conditions, options); + return this.mongooseCollection.deleteMany(this._conditions, options); }; /** @@ -3120,7 +3070,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 +3083,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 +3138,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 +3152,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. @@ -3291,16 +3240,6 @@ Query.prototype.findOneAndUpdate = function(filter, doc, options) { */ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { - // For backwards compability with Mongoose 6 re: #13550 - - if (this._mongooseOptions.overwrite != null) { - printOverwriteDeprecationWarning(); - } - if (this._mongooseOptions.overwrite) { - this.op = 'findOneAndReplace'; - return this._findOneAndReplace(); - } - this._castConditions(); _castArrayFilters(this); @@ -3312,10 +3251,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; } @@ -3323,7 +3258,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { convertNewToReturnDocument(options); this._applyTranslateAliases(options); - this._update = this._castUpdate(this._update, false); + this._update = this._castUpdate(this._update); const _opts = Object.assign({}, options, { setDefaultsOnInsert: this._mongooseOptions.setDefaultsOnInsert @@ -3362,11 +3297,11 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { this._update = this._update.toBSON(); } - let res = await this._collection.collection.findOneAndUpdate(this._conditions, this._update, options); + let res = await this.mongooseCollection.findOneAndUpdate(this._conditions, this._update, options); 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 +3313,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 +3326,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 +3344,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,19 +3388,16 @@ 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); this._applyTranslateAliases(options); - let res = await this._collection.collection.findOneAndDelete(filter, options); + let res = await this.mongooseCollection.findOneAndDelete(filter, options); 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 +3422,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 +3442,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. @@ -3614,7 +3491,6 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options) { options.returnOriginal = returnOriginal; } this.setOptions(options); - this.setOptions({ overwrite: true }); return this; }; @@ -3645,9 +3521,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) { @@ -3673,13 +3546,13 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() { throw validationError; } - let res = await this._collection.collection.findOneAndReplace(filter, this._update, options); + let res = await this.mongooseCollection.findOneAndReplace(filter, this._update, options); 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) => { if (err) { @@ -3711,19 +3584,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 +3634,7 @@ function _completeOneLean(schema, doc, path, res, opts, callback) { return; } } - if (opts.rawResult) { + if (opts.includeResultMetadata) { return callback(null, res); } return callback(null, doc); @@ -3865,16 +3725,11 @@ async function _updateThunk(op) { this._applyTranslateAliases(options); this._update = clone(this._update, options); - const isOverwriting = this._mongooseOptions.overwrite && !hasDollarKeys(this._update); + const isOverwriting = op === 'replaceOne'; if (isOverwriting) { - if (op === 'updateOne' || op === 'updateMany') { - throw new MongooseError('The MongoDB server disallows ' + - 'overwriting documents using `' + op + '`. See: ' + - 'https://mongoosejs.com/docs/deprecations.html#update'); - } this._update = new this.model(this._update, null, true); } else { - this._update = this._castUpdate(this._update, this._mongooseOptions.overwrite); + this._update = this._castUpdate(this._update); if (this._update == null || Object.keys(this._update).length === 0) { return { acknowledged: false }; @@ -3900,7 +3755,7 @@ async function _updateThunk(op) { this._update = this._update.toBSON(); } - return this._collection.collection[op](castedQuery, this._update, options); + return this.mongooseCollection[op](castedQuery, this._update, options); } /** @@ -4175,7 +4030,6 @@ Query.prototype.replaceOne = function(conditions, doc, options, callback) { callback = undefined; } - this.setOptions({ overwrite: true }); return _update(this, 'replaceOne', conditions, doc, options, callback); }; @@ -4224,7 +4078,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 +4160,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 +4272,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); @@ -4666,7 +4527,6 @@ Query.prototype.post = function(fn) { * Casts obj for an update command. * * @param {Object} obj - * @param {Boolean} overwrite * @return {Object} obj after casting its values * @method _castUpdate * @memberOf Query @@ -4674,7 +4534,7 @@ Query.prototype.post = function(fn) { * @api private */ -Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { +Query.prototype._castUpdate = function _castUpdate(obj) { let schema = this.schema; const discriminatorKey = schema.options.discriminatorKey; @@ -4708,7 +4568,6 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { } return castUpdate(schema, obj, { - overwrite: overwrite, strict: this._mongooseOptions.strict, upsert: upsert, arrayFilters: this.options.arrayFilters, @@ -5100,7 +4959,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 75b2399be86..9ba9aa7401b 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 SchemaTypeOptions = require('./options/SchemaTypeOptions'); -const VirtualOptions = require('./options/VirtualOptions'); -const VirtualType = require('./virtualtype'); +const SchemaType = require('./schemaType'); +const SchemaTypeOptions = require('./options/schemaTypeOptions'); +const VirtualOptions = require('./options/virtualOptions'); +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/array.js b/lib/schema/array.js index 6d1c6de9628..f0bcfe6c150 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -7,8 +7,8 @@ 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 SchemaArrayOptions = require('../options/schemaArrayOptions'); +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..15e618215f0 100644 --- a/lib/schema/buffer.js +++ b/lib/schema/buffer.js @@ -5,8 +5,8 @@ 'use strict'; const MongooseBuffer = require('../types/buffer'); -const SchemaBufferOptions = require('../options/SchemaBufferOptions'); -const SchemaType = require('../schematype'); +const SchemaBufferOptions = require('../options/schemaBufferOptions'); +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..406c3b62607 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -5,8 +5,8 @@ 'use strict'; const MongooseError = require('../error/index'); -const SchemaDateOptions = require('../options/SchemaDateOptions'); -const SchemaType = require('../schematype'); +const SchemaDateOptions = require('../options/schemaDateOptions'); +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 90% rename from lib/schema/documentarray.js rename to lib/schema/documentArray.js index a15a1ca2e79..264bcc4e645 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 DocumentArrayElement = require('./documentArrayElement'); const EventEmitter = require('events').EventEmitter; +const SchemaArray = require('./array'); const SchemaDocumentArrayOptions = - require('../options/SchemaDocumentArrayOptions'); -const SchemaType = require('../schematype'); + require('../options/schemaDocumentArrayOptions'); +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 || {}; @@ -102,7 +102,7 @@ function DocumentArrayPath(key, schema, options, schemaOptions) { * * @api public */ -DocumentArrayPath.schemaName = 'DocumentArray'; +SchemaDocumentArray.schemaName = 'DocumentArray'; /** * Options for all document arrays. @@ -112,21 +112,21 @@ 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 */ function _createConstructor(schema, options, baseClass) { - Subdocument || (Subdocument = require('../types/ArraySubdocument')); + Subdocument || (Subdocument = require('../types/arraySubdocument')); // compile an embedded document for this schema function EmbeddedDocument() { @@ -188,7 +188,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); } @@ -225,9 +225,9 @@ 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')); + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray')); const _this = this; try { @@ -301,7 +301,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; @@ -350,7 +350,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; @@ -364,7 +364,7 @@ DocumentArrayPath.prototype.getDefault = function(scope, init, options) { } // lazy load - MongooseDocumentArray || (MongooseDocumentArray = require('../types/DocumentArray')); + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray')); if (!Array.isArray(ret)) { ret = [ret]; @@ -401,9 +401,9 @@ 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')); + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray')); // Skip casting if `value` is the same as the previous value, no need to cast. See gh-9266 if (value != null && value[arrayPathSymbol] != null && value === prev) { @@ -418,7 +418,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 @@ -526,7 +526,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(); @@ -542,7 +542,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); }; @@ -591,7 +591,7 @@ function scopePaths(array, fields, init) { * ignore */ -DocumentArrayPath.defaultOptions = {}; +SchemaDocumentArray.defaultOptions = {}; /** * Sets a default option for all DocumentArray instances. @@ -609,9 +609,9 @@ DocumentArrayPath.defaultOptions = {}; * @api public */ -DocumentArrayPath.set = SchemaType.set; +SchemaDocumentArray.set = SchemaType.set; -DocumentArrayPath.setters = []; +SchemaDocumentArray.setters = []; /** * Attaches a getter for all DocumentArrayPath instances @@ -623,10 +623,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/DocumentArrayElement.js b/lib/schema/documentArrayElement.js similarity index 69% rename from lib/schema/DocumentArrayElement.js rename to 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/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..1c7c41ae900 100644 --- a/lib/schema/map.js +++ b/lib/schema/map.js @@ -5,13 +5,13 @@ */ const MongooseMap = require('../types/map'); -const SchemaMapOptions = require('../options/SchemaMapOptions'); -const SchemaType = require('../schematype'); +const SchemaMapOptions = require('../options/schemaMapOptions'); +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..fbc8c9c5bde 100644 --- a/lib/schema/number.js +++ b/lib/schema/number.js @@ -5,8 +5,8 @@ */ const MongooseError = require('../error/index'); -const SchemaNumberOptions = require('../options/SchemaNumberOptions'); -const SchemaType = require('../schematype'); +const SchemaNumberOptions = require('../options/schemaNumberOptions'); +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 83% rename from lib/schema/objectid.js rename to lib/schema/objectId.js index f0a0b6be747..6368c659002 100644 --- a/lib/schema/objectid.js +++ b/lib/schema/objectId.js @@ -4,8 +4,8 @@ 'use strict'; -const SchemaObjectIdOptions = require('../options/SchemaObjectIdOptions'); -const SchemaType = require('../schematype'); +const SchemaObjectIdOptions = require('../options/schemaObjectIdOptions'); +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..b676c978ca3 100644 --- a/lib/schema/string.js +++ b/lib/schema/string.js @@ -4,9 +4,9 @@ * Module dependencies. */ -const SchemaType = require('../schematype'); +const SchemaType = require('../schemaType'); const MongooseError = require('../error/index'); -const SchemaStringOptions = require('../options/SchemaStringOptions'); +const SchemaStringOptions = require('../options/schemaStringOptions'); const castString = require('../cast/string'); const utils = require('../utils'); const isBsonType = require('../helpers/isBsonType'); @@ -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 17f8a5aa79b..056cb7624c6 100644 --- a/lib/schema/SubdocumentPath.js +++ b/lib/schema/subdocument.js @@ -7,8 +7,8 @@ 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 SchemaSubdocumentOptions = require('../options/schemaSubdocumentOptions'); +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; @@ -67,9 +67,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 @@ -77,11 +77,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; @@ -91,7 +91,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; @@ -129,7 +129,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) }; }; @@ -137,19 +137,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 @@ -158,7 +158,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; } @@ -209,7 +209,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]; @@ -253,7 +253,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)) { @@ -285,7 +285,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) { @@ -319,7 +319,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' @@ -341,15 +341,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 @@ -359,12 +359,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} @@ -373,13 +373,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 }; }; @@ -387,7 +387,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 99% rename from lib/schematype.js rename to lib/schemaType.js index c7d27c6a467..e14b2b047f8 100644 --- a/lib/schematype.js +++ b/lib/schemaType.js @@ -5,7 +5,7 @@ */ const MongooseError = require('./error/index'); -const SchemaTypeOptions = require('./options/SchemaTypeOptions'); +const SchemaTypeOptions = require('./options/schemaTypeOptions'); const $exists = require('./schema/operators/exists'); const $type = require('./schema/operators/type'); const clone = require('./helpers/clone'); 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/types/array/methods/index.js b/lib/types/array/methods/index.js index deb4e801f29..06a985bc15b 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -1,7 +1,7 @@ 'use strict'; const Document = require('../../../document'); -const ArraySubdocument = require('../../ArraySubdocument'); +const ArraySubdocument = require('../../arraySubdocument'); const MongooseError = require('../../../error/mongooseError'); const cleanModifiedSubpaths = require('../../../helpers/document/cleanModifiedSubpaths'); const clone = require('../../../helpers/clone'); diff --git a/lib/types/ArraySubdocument.js b/lib/types/arraySubdocument.js similarity index 100% rename from lib/types/ArraySubdocument.js rename to lib/types/arraySubdocument.js diff --git a/lib/types/DocumentArray/index.js b/lib/types/documentArray/index.js similarity index 100% rename from lib/types/DocumentArray/index.js rename to lib/types/documentArray/index.js diff --git a/lib/types/DocumentArray/isMongooseDocumentArray.js b/lib/types/documentArray/isMongooseDocumentArray.js similarity index 100% rename from lib/types/DocumentArray/isMongooseDocumentArray.js rename to lib/types/documentArray/isMongooseDocumentArray.js diff --git a/lib/types/DocumentArray/methods/index.js b/lib/types/documentArray/methods/index.js similarity index 100% rename from lib/types/DocumentArray/methods/index.js rename to lib/types/documentArray/methods/index.js diff --git a/lib/types/index.js b/lib/types/index.js index 8babdf4b4d1..d234f6bb62a 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -9,9 +9,9 @@ exports.Array = require('./array'); exports.Buffer = require('./buffer'); exports.Document = // @deprecate -exports.Embedded = require('./ArraySubdocument'); +exports.Embedded = require('./arraySubdocument'); -exports.DocumentArray = require('./DocumentArray'); +exports.DocumentArray = require('./documentArray'); exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); diff --git a/lib/utils.js b/lib/utils.js index 4fca9502d56..c1249184296 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -8,12 +8,12 @@ const UUID = require('bson').UUID; const ms = require('ms'); const mpath = require('mpath'); const ObjectId = require('./types/objectid'); -const PopulateOptions = require('./options/PopulateOptions'); +const PopulateOptions = require('./options/populateOptions'); const clone = require('./helpers/clone'); const immediate = require('./helpers/immediate'); const isObject = require('./helpers/isObject'); const isMongooseArray = require('./types/array/isMongooseArray'); -const isMongooseDocumentArray = require('./types/DocumentArray/isMongooseDocumentArray'); +const isMongooseDocumentArray = require('./types/documentArray/isMongooseDocumentArray'); const isBsonType = require('./helpers/isBsonType'); const getFunctionName = require('./helpers/getFunctionName'); const isMongooseObject = require('./helpers/isMongooseObject'); 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 0d04d2975e4..21e9ca2feba 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ ], "license": "MIT", "dependencies": { - "bson": "^5.5.0", + "bson": "^6.2.0", "kareem": "2.5.1", - "mongodb": "5.9.0", + "mongodb": "6.2.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -30,8 +30,8 @@ "devDependencies": { "@babel/core": "7.23.0", "@babel/preset-env": "7.22.20", - "@typescript-eslint/eslint-plugin": "5.62.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", @@ -46,7 +46,7 @@ "dotenv": "16.3.1", "dox": "1.0.0", "eslint": "8.50.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", @@ -118,7 +118,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 ed340f3be8e..59438c61879 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -8,7 +8,7 @@ const start = require('./common'); const Document = require('../lib/document'); const EventEmitter = require('events').EventEmitter; -const ArraySubdocument = require('../lib/types/ArraySubdocument'); +const ArraySubdocument = require('../lib/types/arraySubdocument'); const Query = require('../lib/query'); const assert = require('assert'); const idGetter = require('../lib/helpers/schema/idGetter'); @@ -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; @@ -1984,7 +1985,7 @@ describe('document', function() { const err = await person.save().then(() => null, err => err); assert.equal(err instanceof DocumentNotFoundError, true); - assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`); + assert.equal(err.message, `No document found for query "{ _id: new ObjectId('${person._id}') }" on model "Person"`); }); it('saving a document when version bump required, throws a VersionError when document is not found (gh-10974)', async function() { @@ -2019,7 +2020,7 @@ describe('document', function() { } catch (err) { assert.equal(err instanceof DocumentNotFoundError, true); - assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`); + assert.equal(err.message, `No document found for query "{ _id: new ObjectId('${person._id}') }" on model "Person"`); threw = 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: { @@ -3249,7 +3243,7 @@ describe('document', function() { field1: { type: Number, default: 1 } } } - }); + }, { minimize: false }); const MyModel = db.model('Test', schema); @@ -3268,7 +3262,7 @@ describe('document', function() { field1: { type: Number, default: 1 } } } - }); + }, { minimize: false }); const MyModel = db.model('Test', schema); @@ -5008,7 +5002,7 @@ describe('document', function() { }). then(function(doc) { doc.child = {}; - return doc.save(); + return Parent.updateOne({ _id: doc._id }, { $set: { child: {} } }, { minimize: false }); }). then(function() { return Parent.findOne(); @@ -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 @@ -12239,19 +12233,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: @@ -12502,6 +12483,29 @@ describe('document', function() { assert.strictEqual(parent.toObject().child.concreteProp, 123); }); + it('avoids saving changes to deselected paths (gh-13145) (gh-13062)', async function() { + const testSchema = new mongoose.Schema({ + name: { type: String, required: true }, + age: { type: Number, required: true, select: false }, + links: { type: String, required: true, select: false } + }); + + const Test = db.model('Test', testSchema); + + const { _id } = await Test.create({ + name: 'Test Testerson', + age: 0, + links: 'some init links' + }); + + const doc = await Test.findById(_id); + doc.links = undefined; + const err = await doc.save().then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['links']); + assert.equal(err.errors['links'].message, 'Path `links` is required.'); + }); + it('fires pre validate hooks on 4 level single nested subdocs (gh-13876)', async function() { let attachmentSchemaPreValidateCalls = 0; const attachmentSchema = new Schema({ name: String }); diff --git a/test/helpers/isSimpleValidator.test.js b/test/helpers/isSimpleValidator.test.js index e76d4c4bcb2..4e44ecb5986 100644 --- a/test/helpers/isSimpleValidator.test.js +++ b/test/helpers/isSimpleValidator.test.js @@ -4,7 +4,7 @@ const assert = require('assert'); require('../common'); // required for side-effect setup (so that the default driver is set-up) const isSimpleValidator = require('../../lib/helpers/isSimpleValidator'); -const MongooseDocumentArray = require('../../lib/types/DocumentArray'); +const MongooseDocumentArray = require('../../lib/types/documentArray'); describe('isSimpleValidator', function() { it('empty object', function() { diff --git a/test/index.test.js b/test/index.test.js index d20b8c051f8..27f975a9d21 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -485,7 +485,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); @@ -758,7 +758,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())); @@ -939,7 +939,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 0ded87750a3..8e8d529e2df 100644 --- a/test/model.discriminator.test.js +++ b/test/model.discriminator.test.js @@ -2180,6 +2180,30 @@ 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); + }); + it('should not fail when using a discriminator key multiple times (gh-13906)', async function() { const options = { discriminatorKey: 'type' }; const eventSchema = new Schema({ date: Schema.Types.Date }, options); 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 ed0afa96408..6296b529ffb 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 e86ea268dfd..b3ad2e21830 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); }); @@ -5089,10 +5093,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 @@ -5361,7 +5361,7 @@ describe('Model', function() { } ]); - const beforeExpirationCount = await Test.count({}); + const beforeExpirationCount = await Test.countDocuments({}); assert.ok(beforeExpirationCount === 12); let intervalid; @@ -5375,7 +5375,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(); } @@ -5385,7 +5385,7 @@ describe('Model', function() { clearInterval(intervalid); - const afterExpirationCount = await Test.count({}); + const afterExpirationCount = await Test.countDocuments({}); assert.equal(afterExpirationCount, 0); }); @@ -5515,7 +5515,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.updateOne.test.js b/test/model.updateOne.test.js index 138b2ce43cb..da18c7ceb47 100644 --- a/test/model.updateOne.test.js +++ b/test/model.updateOne.test.js @@ -874,22 +874,6 @@ describe('model: updateOne:', function() { }); }); - it('works with $set and overwrite (gh-2515)', async function() { - const schema = new Schema({ breakfast: String }); - const M = db.model('Test', schema); - - let doc = await M.create({ breakfast: 'bacon' }); - await M.updateOne( - { _id: doc._id }, - { $set: { breakfast: 'eggs' } }, - { overwrite: true } - ); - - doc = await M.findOne({ _id: doc._id }); - assert.equal(doc.breakfast, 'eggs'); - - }); - it('successfully casts set with nested mixed objects (gh-2796)', async function() { const schema = new Schema({ breakfast: {} }); const M = db.model('Test', schema); @@ -1246,33 +1230,6 @@ describe('model: updateOne:', function() { assert.equal(doc.name, 'Val'); }); - it('casting $push with overwrite (gh-3564)', async function() { - const schema = mongoose.Schema({ - topicId: Number, - name: String, - followers: [Number] - }); - - let doc = { - topicId: 100, - name: 'name', - followers: [500] - }; - - const M = db.model('Test', schema); - - await M.create(doc); - - const update = { $push: { followers: 200 } }; - const opts = { overwrite: true, new: true, upsert: false, multi: false }; - - await M.updateOne({ topicId: doc.topicId }, update, opts); - doc = await M.findOne({ topicId: doc.topicId }); - assert.equal(doc.name, 'name'); - assert.deepEqual(doc.followers.toObject(), [500, 200]); - - }); - it('$push with buffer doesnt throw error (gh-3890)', async function() { const InfoSchema = new Schema({ prop: { type: Buffer } @@ -1747,31 +1704,6 @@ describe('model: updateOne:', function() { assert.deepEqual(doc.profiles[0], { rules: {} }); }); - it('with overwrite and upsert (gh-4749) (gh-5631)', function() { - const schema = new Schema({ - name: String, - meta: { age: { type: Number } } - }); - const User = db.model('User', schema); - - const filter = { name: 'Bar' }; - const update = { name: 'Bar', meta: { age: 33 } }; - const options = { overwrite: true, upsert: true }; - const q2 = User.updateOne(filter, update, options); - assert.deepEqual(q2.getUpdate(), { - __v: 0, - meta: { age: 33 }, - name: 'Bar' - }); - - const q3 = User.findOneAndUpdate(filter, update, options); - assert.deepEqual(q3.getUpdate(), { - __v: 0, - meta: { age: 33 }, - name: 'Bar' - }); - }); - it('findOneAndUpdate with nested arrays (gh-5032)', async function() { const schema = Schema({ name: String, 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 b69f0ea374e..5cba1f62c1e 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', @@ -2966,17 +2962,6 @@ describe('Query', function() { delete db.base.options.maxTimeMS; }); - it('throws error with updateOne() and overwrite (gh-7475)', function() { - const Model = db.model('Test', Schema({ name: String })); - - return Model.updateOne({}, { name: 'bar' }, { overwrite: true }).then( - () => { throw new Error('Should have failed'); }, - err => { - assert.ok(err.message.indexOf('updateOne') !== -1); - } - ); - }); - describe('merge()', function() { it('copies populate() (gh-1790)', async function() { const Car = db.model('Car', { @@ -4160,16 +4145,6 @@ describe('Query', function() { /Query must have `op` before executing/ ); }); - it('converts findOneAndUpdate to findOneAndReplace if overwrite set (gh-13550)', async function() { - const testSchema = new Schema({ - name: { type: String } - }); - - const Test = db.model('Test', testSchema); - const q = Test.findOneAndUpdate({}, { name: 'bar' }, { overwrite: true }); - await q.exec(); - assert.equal(q.op, 'findOneAndReplace'); - }); it('allows deselecting discriminator key (gh-13760) (gh-13679)', async function() { const testSchema = new Schema({ name: String, age: Number }); 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 f41620101dc..4c4437f5ae3 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'); }); @@ -2166,7 +2166,7 @@ describe('schema', function() { }); it('SchemaStringOptions line up with schema/string (gh-8256)', function() { - const SchemaStringOptions = require('../lib/options/SchemaStringOptions'); + const SchemaStringOptions = require('../lib/options/schemaStringOptions'); const keys = Object.keys(SchemaStringOptions.prototype). filter(key => key !== 'constructor' && key !== 'populate'); const functions = Object.keys(Schema.Types.String.prototype). @@ -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.document.test.js b/test/types.document.test.js index bc8446e77e7..8a87b06917e 100644 --- a/test/types.document.test.js +++ b/test/types.document.test.js @@ -9,9 +9,9 @@ const start = require('./common'); const assert = require('assert'); const mongoose = start.mongoose; -const ArraySubdocument = require('../lib/types/ArraySubdocument'); +const ArraySubdocument = require('../lib/types/arraySubdocument'); const EventEmitter = require('events').EventEmitter; -const DocumentArray = require('../lib/types/DocumentArray'); +const DocumentArray = require('../lib/types/documentArray'); const Schema = mongoose.Schema; const ValidationError = mongoose.Document.ValidationError; diff --git a/test/types.documentarray.test.js b/test/types.documentarray.test.js index 073f34e4096..4412f2cda97 100644 --- a/test/types.documentarray.test.js +++ b/test/types.documentarray.test.js @@ -6,8 +6,8 @@ const start = require('./common'); -const DocumentArray = require('../lib/types/DocumentArray'); -const ArraySubdocument = require('../lib/types/ArraySubdocument'); +const DocumentArray = require('../lib/types/documentArray'); +const ArraySubdocument = require('../lib/types/arraySubdocument'); const assert = require('assert'); const idGetter = require('../lib/helpers/schema/idGetter'); const setValue = require('../lib/utils').setValue; diff --git a/test/types.map.test.js b/test/types.map.test.js index c08482819d2..3b22beccc56 100644 --- a/test/types.map.test.js +++ b/test/types.map.test.js @@ -6,7 +6,7 @@ const start = require('./common'); -const SchemaMapOptions = require('../lib/options/SchemaMapOptions'); +const SchemaMapOptions = require('../lib/options/schemaMapOptions'); const assert = require('assert'); const mongoose = start.mongoose; diff --git a/test/types/maps.test.ts b/test/types/maps.test.ts index 496a7072830..69cd016c74f 100644 --- a/test/types/maps.test.ts +++ b/test/types/maps.test.ts @@ -83,5 +83,5 @@ function gh13755() { const TestModel = model('Test', testSchema); const doc = new TestModel(); - expectType | undefined>(doc.instance); + expectType | undefined | null>(doc.instance); } 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/models.test.ts b/test/types/models.test.ts index 7865a5e6855..bbfbc1517cf 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -492,7 +492,7 @@ function gh12100() { const TestModel = model('test', schema_with_string_id); const obj = new TestModel(); - expectType(obj._id); + expectType(obj._id); })(); (async function gh12094() { @@ -659,7 +659,7 @@ async function gh13705() { const schema = new Schema({ name: String }); const TestModel = model('Test', schema); - type ExpectedLeanDoc = (mongoose.FlattenMaps<{ name?: string }> & { _id: mongoose.Types.ObjectId }); + type ExpectedLeanDoc = (mongoose.FlattenMaps<{ name?: string | null }> & { _id: mongoose.Types.ObjectId }); const findByIdRes = await TestModel.findById('0'.repeat(24), undefined, { lean: true }); expectType(findByIdRes); @@ -744,10 +744,30 @@ function gh13957() { } interface ITest { - name?: string + name: string } - const schema = new Schema({ name: String }); + const schema = new Schema({ name: { type: String, required: true } }); const TestModel = model('Test', schema); const repository = new RepositoryBase(TestModel); expectType>(repository.insertMany([{ name: 'test' }])); } + +function gh13897() { + interface IDocument { + name: string; + createdAt: Date; + updatedAt: Date; + } + + const documentSchema = new Schema({ + name: { type: String, required: true } + }, + { + timestamps: true + }); + + const Document = model('Document', documentSchema); + const doc = new Document({ name: 'foo' }); + expectType(doc.createdAt); + expectError(new Document({ name: 'foo' })); +} diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index efca12618a5..9c81551c2c1 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() { @@ -392,7 +393,8 @@ async function gh12342_manual() { async function gh12342_auto() { interface Project { - name?: string, stars?: number + name?: string | null, + stars?: number | null } const ProjectSchema = new Schema({ @@ -422,7 +424,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); @@ -482,8 +484,8 @@ async function gh13224() { const UserModel = model('User', userSchema); const u1 = await UserModel.findOne().select(['name']).orFail(); - expectType(u1.name); - expectType(u1.age); + expectType(u1.name); + expectType(u1.age); expectAssignable(u1.toObject); const u2 = await UserModel.findOne().select<{ name?: string }>(['name']).orFail(); diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index 611e9543977..dc87e0a669b 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -10,7 +10,7 @@ type ITest = ReturnType<(typeof Test)['hydrate']>; Test.find().cursor(). eachAsync(async(doc: ITest) => { expectType(doc._id); - expectType(doc.name); + expectType(doc.name); }). then(() => console.log('Done!')); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index fc0204e2a71..c0abc84a031 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -372,50 +372,50 @@ export function autoTypedSchema() { } type TestSchemaType = { - string1?: string; - string2?: string; - string3?: string; - string4?: string; + string1?: string | null; + string2?: string | null; + string3?: string | null; + string4?: string | null; string5: string; - number1?: number; - number2?: number; - number3?: number; - number4?: number; + number1?: number | null; + number2?: number | null; + number3?: number | null; + number4?: number | null; number5: number; - date1?: Date; - date2?: Date; - date3?: Date; - date4?: Date; + date1?: Date | null; + date2?: Date | null; + date3?: Date | null; + date4?: Date | null; date5: Date; - buffer1?: Buffer; - buffer2?: Buffer; - buffer3?: Buffer; - buffer4?: Buffer; - boolean1?: boolean; - boolean2?: boolean; - boolean3?: boolean; - boolean4?: boolean; + buffer1?: Buffer | null; + buffer2?: Buffer | null; + buffer3?: Buffer | null; + buffer4?: Buffer | null; + boolean1?: boolean | null; + boolean2?: boolean | null; + boolean3?: boolean | null; + boolean4?: boolean | null; boolean5: boolean; - mixed1?: any; - mixed2?: any; - mixed3?: any; - objectId1?: Types.ObjectId; - objectId2?: Types.ObjectId; - objectId3?: Types.ObjectId; - customSchema?: Int8; - map1?: Map; - map2?: Map; + mixed1?: any | null; + mixed2?: any | null; + mixed3?: any | null; + objectId1?: Types.ObjectId | null; + objectId2?: Types.ObjectId | null; + objectId3?: Types.ObjectId | null; + customSchema?: Int8 | null; + map1?: Map | null; + map2?: Map | null; array1: string[]; array2: any[]; array3: any[]; array4: any[]; array5: any[]; array6: string[]; - array7?: string[]; - array8?: string[]; - decimal1?: Types.Decimal128; - decimal2?: Types.Decimal128; - decimal3?: Types.Decimal128; + array7?: string[] | null; + array8?: string[] | null; + decimal1?: Types.Decimal128 | null; + decimal2?: Types.Decimal128 | null; + decimal3?: Types.Decimal128 | null; }; const TestSchema = new Schema({ @@ -546,17 +546,17 @@ export function autoTypedSchema() { export type AutoTypedSchemaType = { schema: { userName: string; - description?: string; + description?: string | null; nested?: { age: number; - hobby?: string - }, - favoritDrink?: 'Tea' | 'Coffee', + hobby?: string | null + } | null, + favoritDrink?: 'Tea' | 'Coffee' | null, favoritColorMode: 'dark' | 'light' - friendID?: Types.ObjectId; + friendID?: Types.ObjectId | null; nestedArray: Types.DocumentArray<{ date: Date; - messages?: number; + messages?: number | null; }> } , statics: { @@ -634,7 +634,7 @@ function gh12003() { type TSchemaOptions = ResolveSchemaOptions>; expectType<'type'>({} as TSchemaOptions['typeKey']); - expectType<{ name?: string }>({} as BaseSchemaType); + expectType<{ name?: string | null }>({} as BaseSchemaType); } function gh11987() { @@ -670,7 +670,7 @@ function gh12030() { } ]>; expectType<{ - username?: string + username?: string | null }[]>({} as A); type B = ObtainDocumentType<{ @@ -682,13 +682,13 @@ function gh12030() { }>; expectType<{ users: { - username?: string + username?: string | null }[]; }>({} as B); expectType<{ users: { - username?: string + username?: string | null }[]; }>({} as InferSchemaType); @@ -710,7 +710,7 @@ function gh12030() { expectType<{ users: Types.DocumentArray<{ credit: number; - username?: string; + username?: string | null; }>; }>({} as InferSchemaType); @@ -719,7 +719,7 @@ function gh12030() { data: { type: { role: String }, default: {} } }); - expectType<{ data: { role?: string } }>({} as InferSchemaType); + expectType<{ data: { role?: string | null } }>({} as InferSchemaType); const Schema5 = new Schema({ data: { type: { role: Object }, default: {} } @@ -744,7 +744,7 @@ function gh12030() { track?: { backupCount: number; count: number; - }; + } | null; }>({} as InferSchemaType); } @@ -821,7 +821,7 @@ function gh12450() { }); expectType<{ - user?: Types.ObjectId; + user?: Types.ObjectId | null; }>({} as InferSchemaType); const Schema2 = new Schema({ @@ -836,14 +836,14 @@ function gh12450() { decimalValue: { type: Schema.Types.Decimal128 } }); - expectType<{ createdAt: Date, decimalValue?: Types.Decimal128 }>({} as InferSchemaType); + expectType<{ createdAt: Date, decimalValue?: Types.Decimal128 | null }>({} as InferSchemaType); const Schema4 = new Schema({ createdAt: { type: Date }, decimalValue: { type: Schema.Types.Decimal128 } }); - expectType<{ createdAt?: Date, decimalValue?: Types.Decimal128 }>({} as InferSchemaType); + expectType<{ createdAt?: Date | null, decimalValue?: Types.Decimal128 | null }>({} as InferSchemaType); } function gh12242() { @@ -867,13 +867,13 @@ function testInferTimestamps() { // an error "Parameter type { createdAt: Date; updatedAt: Date; name?: string | undefined; } // is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } & // { name?: string | undefined; }" - expectType<{ createdAt: Date, updatedAt: Date } & { name?: string }>({} as WithTimestamps); + expectType<{ createdAt: Date, updatedAt: Date } & { name?: string | null }>({} as WithTimestamps); const schema2 = new Schema({ name: String }, { timestamps: true, - methods: { myName(): string | undefined { + methods: { myName(): string | undefined | null { return this.name; } } }); @@ -883,7 +883,7 @@ function testInferTimestamps() { // an error "Parameter type { createdAt: Date; updatedAt: Date; name?: string | undefined; } // is not identical to argument type { createdAt: NativeDate; updatedAt: NativeDate; } & // { name?: string | undefined; }" - expectType<{ name?: string }>({} as WithTimestamps2); + expectType<{ name?: string | null }>({} as WithTimestamps2); } function gh12431() { @@ -893,25 +893,25 @@ function gh12431() { }); type Example = InferSchemaType; - expectType<{ testDate?: Date, testDecimal?: Types.Decimal128 }>({} as Example); + expectType<{ testDate?: Date | null, testDecimal?: Types.Decimal128 | null }>({} as Example); } async function gh12593() { const testSchema = new Schema({ x: { type: Schema.Types.UUID } }); type Example = InferSchemaType; - expectType<{ x?: Buffer }>({} as Example); + expectType<{ x?: Buffer | null }>({} as Example); const Test = model('Test', testSchema); const doc = await Test.findOne({ x: '4709e6d9-61fd-435e-b594-d748eb196d8f' }).orFail(); - expectType(doc.x); + expectType(doc.x); const doc2 = new Test({ x: '4709e6d9-61fd-435e-b594-d748eb196d8f' }); - expectType(doc2.x); + expectType(doc2.x); const doc3 = await Test.findOne({}).orFail().lean(); - expectType(doc3.x); + expectType(doc3.x); const arrSchema = new Schema({ arr: [{ type: Schema.Types.UUID }] }); @@ -978,7 +978,7 @@ function gh12611() { expectType<{ description: string; skills: Types.ObjectId[]; - anotherField?: string; + anotherField?: string | null; }>({} as Props); } @@ -1181,7 +1181,7 @@ function gh13702() { function gh13780() { const schema = new Schema({ num: Schema.Types.BigInt }); type InferredType = InferSchemaType; - expectType(null as unknown as InferredType['num']); + expectType(null as unknown as InferredType['num']); } function gh13800() { 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/inferschematype.d.ts b/types/inferschematype.d.ts index 77ec76ca4c6..e4ea5ebe327 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -24,13 +24,22 @@ declare module 'mongoose' { * @param {EnforcedDocType} EnforcedDocType A generic type enforced by user "provided before schema constructor". * @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition". */ - type ObtainDocumentType = DefaultSchemaOptions> = - IsItRecordAndNotAny extends true ? EnforcedDocType : { - [K in keyof (RequiredPaths & - OptionalPaths)]: ObtainDocumentPathType; - }; + type ObtainDocumentType< + DocDefinition, + EnforcedDocType = any, + TSchemaOptions extends Record = DefaultSchemaOptions + > = IsItRecordAndNotAny extends true ? + EnforcedDocType : + { + [ + K in keyof (RequiredPaths & + OptionalPaths) + ]: IsPathRequired extends true ? + ObtainDocumentPathType : + ObtainDocumentPathType | null; + }; - /** + /** * @summary Obtains document schema type from Schema instance. * @param {Schema} TSchema `typeof` a schema instance. * @example @@ -39,7 +48,7 @@ declare module 'mongoose' { * // result * type UserType = {userName?: string} */ - export type InferSchemaType = IfAny>; + export type InferSchemaType = IfAny>; /** * @summary Obtains schema Generic type by using generic alias. 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 cfa23bc40da..2a2bb0ff334 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -158,7 +158,7 @@ declare module 'mongoose' { AcceptsDiscriminator, IndexManager, SessionStarter { - new (doc?: DocType, fields?: any | null, options?: boolean | AnyObject): THydratedDocumentType; + new >(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): THydratedDocumentType; aggregate(pipeline?: PipelineStage[], options?: AggregateOptions): Aggregate>; aggregate(pipeline: PipelineStage[]): Aggregate>; @@ -477,8 +477,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, @@ -582,7 +585,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, @@ -620,12 +623,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, @@ -641,7 +638,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, @@ -674,7 +671,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 1d66e75221c..008f82d2d9d 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -124,12 +124,7 @@ declare module 'mongoose' { overwriteDiscriminatorKey?: boolean; projection?: ProjectionType; /** - * @deprecated use includeResultMetadata instead. - * if true, returns the raw result from the MongoDB driver - */ - rawResult?: boolean; - /** - * if ture, includes meta data for the result from the MongoDB driver + * if true, returns the full ModifyResult rather than just the document */ includeResultMetadata?: boolean; readPreference?: string | mongodb.ReadPreferenceMode; @@ -322,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; @@ -397,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, @@ -444,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,