UNPKG

177 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies.
5 */
6
7const Aggregate = require('./aggregate');
8const ChangeStream = require('./cursor/changeStream');
9const Document = require('./document');
10const DocumentNotFoundError = require('./error/notFound');
11const EventEmitter = require('events').EventEmitter;
12const Kareem = require('kareem');
13const MongooseError = require('./error/index');
14const ObjectParameterError = require('./error/objectParameter');
15const OverwriteModelError = require('./error/overwriteModel');
16const Query = require('./query');
17const SaveOptions = require('./options/saveOptions');
18const Schema = require('./schema');
19const ValidationError = require('./error/validation');
20const VersionError = require('./error/version');
21const ParallelSaveError = require('./error/parallelSave');
22const applyDefaultsHelper = require('./helpers/document/applyDefaults');
23const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
24const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
25const applyHooks = require('./helpers/model/applyHooks');
26const applyMethods = require('./helpers/model/applyMethods');
27const applyProjection = require('./helpers/projection/applyProjection');
28const applyReadConcern = require('./helpers/schema/applyReadConcern');
29const applySchemaCollation = require('./helpers/indexes/applySchemaCollation');
30const applyStaticHooks = require('./helpers/model/applyStaticHooks');
31const applyStatics = require('./helpers/model/applyStatics');
32const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
33const assignVals = require('./helpers/populate/assignVals');
34const castBulkWrite = require('./helpers/model/castBulkWrite');
35const clone = require('./helpers/clone');
36const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
37const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
38const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
39const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
40const discriminator = require('./helpers/model/discriminator');
41const each = require('./helpers/each');
42const get = require('./helpers/get');
43const getConstructorName = require('./helpers/getConstructorName');
44const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
45const getModelsMapForPopulate = require('./helpers/populate/getModelsMapForPopulate');
46const immediate = require('./helpers/immediate');
47const internalToObjectOptions = require('./options').internalToObjectOptions;
48const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex');
49const isIndexEqual = require('./helpers/indexes/isIndexEqual');
50const {
51 getRelatedDBIndexes,
52 getRelatedSchemaIndexes
53} = require('./helpers/indexes/getRelatedIndexes');
54const decorateDiscriminatorIndexOptions = require('./helpers/indexes/decorateDiscriminatorIndexOptions');
55const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
56const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
57const parallelLimit = require('./helpers/parallelLimit');
58const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
59const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
60const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
61const setDottedPath = require('./helpers/path/setDottedPath');
62const STATES = require('./connectionState');
63const util = require('util');
64const utils = require('./utils');
65const MongooseBulkWriteError = require('./error/bulkWriteError');
66const minimize = require('./helpers/minimize');
67
68const modelCollectionSymbol = Symbol('mongoose#Model#collection');
69const modelDbSymbol = Symbol('mongoose#Model#db');
70const modelSymbol = require('./helpers/symbols').modelSymbol;
71const subclassedSymbol = Symbol('mongoose#Model#subclassed');
72
73const { VERSION_INC, VERSION_WHERE, VERSION_ALL } = Document;
74
75const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
76 bson: true,
77 flattenObjectIds: false
78});
79
80/**
81 * A Model is a class that's your primary tool for interacting with MongoDB.
82 * An instance of a Model is called a [Document](https://mongoosejs.com/docs/api/document.html#Document).
83 *
84 * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
85 * class. You should not use the `mongoose.Model` class directly. The
86 * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) and
87 * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()) functions
88 * create subclasses of `mongoose.Model` as shown below.
89 *
90 * #### Example:
91 *
92 * // `UserModel` is a "Model", a subclass of `mongoose.Model`.
93 * const UserModel = mongoose.model('User', new Schema({ name: String }));
94 *
95 * // You can use a Model to create new documents using `new`:
96 * const userDoc = new UserModel({ name: 'Foo' });
97 * await userDoc.save();
98 *
99 * // You also use a model to create queries:
100 * const userFromDb = await UserModel.findOne({ name: 'Foo' });
101 *
102 * @param {Object} doc values for initial set
103 * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()).
104 * @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document.
105 * @inherits Document https://mongoosejs.com/docs/api/document.html
106 * @event `error`: If listening to this event, 'error' is emitted when a document was saved and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
107 * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
108 * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
109 * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed.
110 * @api public
111 */
112
113function Model(doc, fields, skipId) {
114 if (fields instanceof Schema) {
115 throw new TypeError('2nd argument to `Model` constructor must be a POJO or string, ' +
116 '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
117 '`mongoose.Model()`.');
118 }
119 if (typeof doc === 'string') {
120 throw new TypeError('First argument to `Model` constructor must be an object, ' +
121 '**not** a string. Make sure you\'re calling `mongoose.model()`, not ' +
122 '`mongoose.Model()`.');
123 }
124 Document.call(this, doc, fields, skipId);
125}
126
127/**
128 * Inherits from Document.
129 *
130 * All Model.prototype features are available on
131 * top level (non-sub) documents.
132 * @api private
133 */
134
135Object.setPrototypeOf(Model.prototype, Document.prototype);
136Model.prototype.$isMongooseModelPrototype = true;
137
138/**
139 * Connection the model uses.
140 *
141 * @api public
142 * @property db
143 * @memberOf Model
144 * @instance
145 */
146
147Model.prototype.db;
148
149/**
150 * The collection instance this model uses.
151 * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
152 * Using `Model.collection` means you bypass Mongoose middleware, validation, and casting.
153 *
154 * This property is read-only. Modifying this property is a no-op.
155 *
156 * @api public
157 * @property collection
158 * @memberOf Model
159 * @instance
160 */
161
162Model.prototype.collection;
163
164/**
165 * Internal collection the model uses.
166 *
167 * This property is read-only. Modifying this property is a no-op.
168 *
169 * @api private
170 * @property collection
171 * @memberOf Model
172 * @instance
173 */
174
175
176Model.prototype.$__collection;
177
178/**
179 * The name of the model
180 *
181 * @api public
182 * @property modelName
183 * @memberOf Model
184 * @instance
185 */
186
187Model.prototype.modelName;
188
189/**
190 * Additional properties to attach to the query when calling `save()` and
191 * `isNew` is false.
192 *
193 * @api public
194 * @property $where
195 * @memberOf Model
196 * @instance
197 */
198
199Model.prototype.$where;
200
201/**
202 * If this is a discriminator model, `baseModelName` is the name of
203 * the base model.
204 *
205 * @api public
206 * @property baseModelName
207 * @memberOf Model
208 * @instance
209 */
210
211Model.prototype.baseModelName;
212
213/**
214 * Event emitter that reports any errors that occurred. Useful for global error
215 * handling.
216 *
217 * #### Example:
218 *
219 * MyModel.events.on('error', err => console.log(err.message));
220 *
221 * // Prints a 'CastError' because of the above handler
222 * await MyModel.findOne({ _id: 'Not a valid ObjectId' }).catch(noop);
223 *
224 * @api public
225 * @property events
226 * @fires error whenever any query or model function errors
227 * @memberOf Model
228 * @static
229 */
230
231Model.events;
232
233/**
234 * Compiled middleware for this model. Set in `applyHooks()`.
235 *
236 * @api private
237 * @property _middleware
238 * @memberOf Model
239 * @static
240 */
241
242Model._middleware;
243
244/*!
245 * ignore
246 */
247
248function _applyCustomWhere(doc, where) {
249 if (doc.$where == null) {
250 return;
251 }
252 for (const key of Object.keys(doc.$where)) {
253 where[key] = doc.$where[key];
254 }
255}
256
257/*!
258 * ignore
259 */
260
261Model.prototype.$__handleSave = function(options, callback) {
262 const saveOptions = {};
263
264 applyWriteConcern(this.$__schema, options);
265 if (typeof options.writeConcern !== 'undefined') {
266 saveOptions.writeConcern = {};
267 if ('w' in options.writeConcern) {
268 saveOptions.writeConcern.w = options.writeConcern.w;
269 }
270 if ('j' in options.writeConcern) {
271 saveOptions.writeConcern.j = options.writeConcern.j;
272 }
273 if ('wtimeout' in options.writeConcern) {
274 saveOptions.writeConcern.wtimeout = options.writeConcern.wtimeout;
275 }
276 } else {
277 if ('w' in options) {
278 saveOptions.w = options.w;
279 }
280 if ('j' in options) {
281 saveOptions.j = options.j;
282 }
283 if ('wtimeout' in options) {
284 saveOptions.wtimeout = options.wtimeout;
285 }
286 }
287 if ('checkKeys' in options) {
288 saveOptions.checkKeys = options.checkKeys;
289 }
290
291 const session = this.$session();
292 const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore();
293 if (session != null) {
294 saveOptions.session = session;
295 } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) {
296 // Only set session from asyncLocalStorage if `session` option wasn't originally passed in options
297 saveOptions.session = asyncLocalStorage.session;
298 }
299 if (this.$isNew) {
300 // send entire doc
301 const obj = this.toObject(saveToObjectOptions);
302 if ((obj || {})._id === void 0) {
303 // documents must have an _id else mongoose won't know
304 // what to update later if more changes are made. the user
305 // wouldn't know what _id was generated by mongodb either
306 // nor would the ObjectId generated by mongodb necessarily
307 // match the schema definition.
308 immediate(function() {
309 callback(new MongooseError('document must have an _id before saving'));
310 });
311 return;
312 }
313
314 this.$__version(true, obj);
315 this[modelCollectionSymbol].insertOne(obj, saveOptions).then(
316 ret => callback(null, ret),
317 err => {
318 _setIsNew(this, true);
319
320 callback(err, null);
321 }
322 );
323
324 this.$__reset();
325 _setIsNew(this, false);
326 // Make it possible to retry the insert
327 this.$__.inserting = true;
328 return;
329 }
330
331 // Make sure we don't treat it as a new object on error,
332 // since it already exists
333 this.$__.inserting = false;
334 const delta = this.$__delta();
335
336 if (options.pathsToSave) {
337 for (const key in delta[1]['$set']) {
338 if (options.pathsToSave.includes(key)) {
339 continue;
340 } else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
341 continue;
342 } else {
343 delete delta[1]['$set'][key];
344 }
345 }
346 }
347 if (delta) {
348 if (delta instanceof MongooseError) {
349 callback(delta);
350 return;
351 }
352
353 const where = this.$__where(delta[0]);
354 if (where instanceof MongooseError) {
355 callback(where);
356 return;
357 }
358
359 _applyCustomWhere(this, where);
360
361 const update = delta[1];
362 if (this.$__schema.options.minimize) {
363 for (const updateOp of Object.values(update)) {
364 if (updateOp == null) {
365 continue;
366 }
367 for (const key of Object.keys(updateOp)) {
368 if (updateOp[key] == null || typeof updateOp[key] !== 'object') {
369 continue;
370 }
371 if (!utils.isPOJO(updateOp[key])) {
372 continue;
373 }
374 minimize(updateOp[key]);
375 if (Object.keys(updateOp[key]).length === 0) {
376 delete updateOp[key];
377 update.$unset = update.$unset || {};
378 update.$unset[key] = 1;
379 }
380 }
381 }
382 }
383
384 this[modelCollectionSymbol].updateOne(where, update, saveOptions).then(
385 ret => {
386 ret.$where = where;
387 callback(null, ret);
388 },
389 err => {
390 this.$__undoReset();
391
392 callback(err);
393 }
394 );
395 } else {
396 handleEmptyUpdate.call(this);
397 return;
398 }
399
400 // store the modified paths before the document is reset
401 this.$__.modifiedPaths = this.modifiedPaths();
402 this.$__reset();
403
404 _setIsNew(this, false);
405
406 function handleEmptyUpdate() {
407 const optionsWithCustomValues = Object.assign({}, options, saveOptions);
408 const where = this.$__where();
409 const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
410 if (optimisticConcurrency && !Array.isArray(optimisticConcurrency)) {
411 const key = this.$__schema.options.versionKey;
412 const val = this.$__getValue(key);
413 if (val != null) {
414 where[key] = val;
415 }
416 }
417
418 applyReadConcern(this.$__schema, optionsWithCustomValues);
419 this.constructor.collection.findOne(where, optionsWithCustomValues)
420 .then(documentExists => {
421 const matchedCount = !documentExists ? 0 : 1;
422 callback(null, { $where: where, matchedCount });
423 })
424 .catch(callback);
425 }
426};
427
428/*!
429 * ignore
430 */
431
432Model.prototype.$__save = function(options, callback) {
433 this.$__handleSave(options, (error, result) => {
434 if (error) {
435 const hooks = this.$__schema.s.hooks;
436 return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
437 callback(error, this);
438 });
439 }
440 let numAffected = 0;
441 const writeConcern = options != null ?
442 options.writeConcern != null ?
443 options.writeConcern.w :
444 options.w :
445 0;
446 if (writeConcern !== 0) {
447 // Skip checking if write succeeded if writeConcern is set to
448 // unacknowledged writes, because otherwise `numAffected` will always be 0
449 if (result != null) {
450 if (Array.isArray(result)) {
451 numAffected = result.length;
452 } else if (result.matchedCount != null) {
453 numAffected = result.matchedCount;
454 } else {
455 numAffected = result;
456 }
457 }
458
459 const versionBump = this.$__.version;
460 // was this an update that required a version bump?
461 if (versionBump && !this.$__.inserting) {
462 const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
463 this.$__.version = undefined;
464 const key = this.$__schema.options.versionKey;
465 const version = this.$__getValue(key) || 0;
466 if (numAffected <= 0) {
467 // the update failed. pass an error back
468 this.$__undoReset();
469 const err = this.$__.$versionError ||
470 new VersionError(this, version, this.$__.modifiedPaths);
471 return callback(err);
472 }
473
474 // increment version if was successful
475 if (doIncrement) {
476 this.$__setValue(key, version + 1);
477 }
478 }
479 if (result != null && numAffected <= 0) {
480 this.$__undoReset();
481 error = new DocumentNotFoundError(result.$where,
482 this.constructor.modelName, numAffected, result);
483 const hooks = this.$__schema.s.hooks;
484 return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
485 callback(error, this);
486 });
487 }
488 }
489 this.$__.saving = undefined;
490 this.$__.savedState = {};
491 this.$emit('save', this, numAffected);
492 this.constructor.emit('save', this, numAffected);
493 callback(null, this);
494 });
495};
496
497/*!
498 * ignore
499 */
500
501function generateVersionError(doc, modifiedPaths) {
502 const key = doc.$__schema.options.versionKey;
503 if (!key) {
504 return null;
505 }
506 const version = doc.$__getValue(key) || 0;
507 return new VersionError(doc, version, modifiedPaths);
508}
509
510/**
511 * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) is `true`,
512 * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation with just the modified paths if `isNew` is `false`.
513 *
514 * #### Example:
515 *
516 * product.sold = Date.now();
517 * product = await product.save();
518 *
519 * If save is successful, the returned promise will fulfill with the document
520 * saved.
521 *
522 * #### Example:
523 *
524 * const newProduct = await product.save();
525 * newProduct === product; // true
526 *
527 * @param {Object} [options] options optional options
528 * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
529 * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
530 * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
531 * @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
532 * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
533 * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
534 * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern).
535 * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names)
536 * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
537 * @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
538 * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
539 * @return {Promise}
540 * @api public
541 * @see middleware https://mongoosejs.com/docs/middleware.html
542 */
543
544Model.prototype.save = async function save(options) {
545 if (typeof options === 'function' || typeof arguments[1] === 'function') {
546 throw new MongooseError('Model.prototype.save() no longer accepts a callback');
547 }
548
549 let parallelSave;
550 this.$op = 'save';
551
552 if (this.$__.saving) {
553 parallelSave = new ParallelSaveError(this);
554 } else {
555 this.$__.saving = new ParallelSaveError(this);
556 }
557
558 options = new SaveOptions(options);
559 if (options.hasOwnProperty('session')) {
560 this.$session(options.session);
561 }
562 if (this.$__.timestamps != null) {
563 options.timestamps = this.$__.timestamps;
564 }
565 this.$__.$versionError = generateVersionError(this, this.modifiedPaths());
566
567 if (parallelSave) {
568 this.$__handleReject(parallelSave);
569 throw parallelSave;
570 }
571
572 this.$__.saveOptions = options;
573
574 await new Promise((resolve, reject) => {
575 this.$__save(options, error => {
576 this.$__.saving = null;
577 this.$__.saveOptions = null;
578 this.$__.$versionError = null;
579 this.$op = null;
580 if (error != null) {
581 this.$__handleReject(error);
582 return reject(error);
583 }
584
585 resolve();
586 });
587 });
588
589 return this;
590};
591
592Model.prototype.$save = Model.prototype.save;
593
594/**
595 * Appends versioning to the where and update clauses.
596 *
597 * @api private
598 * @method $__version
599 * @memberOf Model
600 * @instance
601 */
602
603Model.prototype.$__version = function(where, delta) {
604 const key = this.$__schema.options.versionKey;
605 if (where === true) {
606 // this is an insert
607 if (key) {
608 setDottedPath(delta, key, 0);
609 this.$__setValue(key, 0);
610 }
611 return;
612 }
613
614 if (key === false) {
615 return;
616 }
617
618 // updates
619
620 // only apply versioning if our versionKey was selected. else
621 // there is no way to select the correct version. we could fail
622 // fast here and force them to include the versionKey but
623 // thats a bit intrusive. can we do this automatically?
624
625 if (!this.$__isSelected(key)) {
626 return;
627 }
628
629 // $push $addToSet don't need the where clause set
630 if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
631 const value = this.$__getValue(key);
632 if (value != null) where[key] = value;
633 }
634
635 if (VERSION_INC === (VERSION_INC & this.$__.version)) {
636 if (get(delta.$set, key, null) != null) {
637 // Version key is getting set, means we'll increment the doc's version
638 // after a successful save, so we should set the incremented version so
639 // future saves don't fail (gh-5779)
640 ++delta.$set[key];
641 } else {
642 delta.$inc = delta.$inc || {};
643 delta.$inc[key] = 1;
644 }
645 }
646};
647
648/**
649 * Signal that we desire an increment of this documents version.
650 *
651 * #### Example:
652 *
653 * const doc = await Model.findById(id);
654 * doc.increment();
655 * await doc.save();
656 *
657 * @see versionKeys https://mongoosejs.com/docs/guide.html#versionKey
658 * @memberOf Model
659 * @method increment
660 * @api public
661 */
662
663Model.prototype.increment = function increment() {
664 this.$__.version = VERSION_ALL;
665 return this;
666};
667
668/**
669 * Returns a query object
670 *
671 * @api private
672 * @method $__where
673 * @memberOf Model
674 * @instance
675 */
676
677Model.prototype.$__where = function _where(where) {
678 where || (where = {});
679
680 if (!where._id) {
681 where._id = this._doc._id;
682 }
683
684 if (this._doc._id === void 0) {
685 return new MongooseError('No _id found on document!');
686 }
687
688 return where;
689};
690
691/**
692 * Delete this document from the db.
693 *
694 * #### Example:
695 *
696 * await product.deleteOne();
697 * await Product.findById(product._id); // null
698 *
699 * @return {Query} Query
700 * @api public
701 */
702
703Model.prototype.deleteOne = function deleteOne(options) {
704 if (typeof options === 'function' ||
705 typeof arguments[1] === 'function') {
706 throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
707 }
708
709 if (!options) {
710 options = {};
711 }
712
713 if (options.hasOwnProperty('session')) {
714 this.$session(options.session);
715 }
716
717 const self = this;
718 const where = this.$__where();
719 if (where instanceof Error) {
720 throw where;
721 }
722 const query = self.constructor.deleteOne(where, options);
723
724 if (this.$session() != null) {
725 if (!('session' in query.options)) {
726 query.options.session = this.$session();
727 }
728 }
729
730 query.pre(function queryPreDeleteOne(cb) {
731 self.constructor._middleware.execPre('deleteOne', self, [self], cb);
732 });
733 query.pre(function callSubdocPreHooks(cb) {
734 each(self.$getAllSubdocs(), (subdoc, cb) => {
735 subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], cb);
736 }, cb);
737 });
738 query.pre(function skipIfAlreadyDeleted(cb) {
739 if (self.$__.isDeleted) {
740 return cb(Kareem.skipWrappedFunction());
741 }
742 return cb();
743 });
744 query.post(function callSubdocPostHooks(cb) {
745 each(self.$getAllSubdocs(), (subdoc, cb) => {
746 subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc], {}, cb);
747 }, cb);
748 });
749 query.post(function queryPostDeleteOne(cb) {
750 self.constructor._middleware.execPost('deleteOne', self, [self], {}, cb);
751 });
752
753 return query;
754};
755
756/**
757 * Returns the model instance used to create this document if no `name` specified.
758 * If `name` specified, returns the model with the given `name`.
759 *
760 * #### Example:
761 *
762 * const doc = new Tank({});
763 * doc.$model() === Tank; // true
764 * await doc.$model('User').findById(id);
765 *
766 * @param {String} [name] model name
767 * @method $model
768 * @api public
769 * @return {Model}
770 */
771
772Model.prototype.$model = function $model(name) {
773 if (arguments.length === 0) {
774 return this.constructor;
775 }
776 return this[modelDbSymbol].model(name);
777};
778
779/**
780 * Returns the model instance used to create this document if no `name` specified.
781 * If `name` specified, returns the model with the given `name`.
782 *
783 * #### Example:
784 *
785 * const doc = new Tank({});
786 * doc.$model() === Tank; // true
787 * await doc.$model('User').findById(id);
788 *
789 * @param {String} [name] model name
790 * @method model
791 * @api public
792 * @return {Model}
793 */
794
795Model.prototype.model = Model.prototype.$model;
796
797/**
798 * Returns a document with `_id` only if at least one document exists in the database that matches
799 * the given `filter`, and `null` otherwise.
800 *
801 * Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to
802 * `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean()`
803 *
804 * #### Example:
805 *
806 * await Character.deleteMany({});
807 * await Character.create({ name: 'Jean-Luc Picard' });
808 *
809 * await Character.exists({ name: /picard/i }); // { _id: ... }
810 * await Character.exists({ name: /riker/i }); // null
811 *
812 * This function triggers the following middleware.
813 *
814 * - `findOne()`
815 *
816 * @param {Object} filter
817 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
818 * @return {Query}
819 */
820
821Model.exists = function exists(filter, options) {
822 _checkContext(this, 'exists');
823 if (typeof arguments[2] === 'function') {
824 throw new MongooseError('Model.exists() no longer accepts a callback');
825 }
826
827 const query = this.findOne(filter).
828 select({ _id: 1 }).
829 lean().
830 setOptions(options);
831
832 return query;
833};
834
835/**
836 * Adds a discriminator type.
837 *
838 * #### Example:
839 *
840 * function BaseSchema() {
841 * Schema.apply(this, arguments);
842 *
843 * this.add({
844 * name: String,
845 * createdAt: Date
846 * });
847 * }
848 * util.inherits(BaseSchema, Schema);
849 *
850 * const PersonSchema = new BaseSchema();
851 * const BossSchema = new BaseSchema({ department: String });
852 *
853 * const Person = mongoose.model('Person', PersonSchema);
854 * const Boss = Person.discriminator('Boss', BossSchema);
855 * new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
856 *
857 * const employeeSchema = new Schema({ boss: ObjectId });
858 * const Employee = Person.discriminator('Employee', employeeSchema, 'staff');
859 * new Employee().__t; // "staff" because of 3rd argument above
860 *
861 * @param {String} name discriminator model name
862 * @param {Schema} schema discriminator model schema
863 * @param {Object|String} [options] If string, same as `options.value`.
864 * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
865 * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
866 * @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
867 * @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
868 * @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
869 * @return {Model} The newly created discriminator model
870 * @api public
871 */
872
873Model.discriminator = function(name, schema, options) {
874 let model;
875 if (typeof name === 'function') {
876 model = name;
877 name = utils.getFunctionName(model);
878 if (!(model.prototype instanceof Model)) {
879 throw new MongooseError('The provided class ' + name + ' must extend Model');
880 }
881 }
882
883 options = options || {};
884 const value = utils.isPOJO(options) ? options.value : options;
885 const clone = typeof options.clone === 'boolean' ? options.clone : true;
886 const mergePlugins = typeof options.mergePlugins === 'boolean' ? options.mergePlugins : true;
887 const overwriteModels = typeof options.overwriteModels === 'boolean' ? options.overwriteModels : false;
888
889 _checkContext(this, 'discriminator');
890
891 if (utils.isObject(schema) && !schema.instanceOfSchema) {
892 schema = new Schema(schema);
893 }
894 if (schema instanceof Schema && clone) {
895 schema = schema.clone();
896 }
897
898 schema = discriminator(this, name, schema, value, mergePlugins, options.mergeHooks, overwriteModels);
899 if (this.db.models[name] && !schema.options.overwriteModels && !overwriteModels) {
900 throw new OverwriteModelError(name);
901 }
902
903 schema.$isRootDiscriminator = true;
904 schema.$globalPluginsApplied = true;
905
906 model = this.db.model(model || name, schema, this.$__collection.name);
907 this.discriminators[name] = model;
908 const d = this.discriminators[name];
909 Object.setPrototypeOf(d.prototype, this.prototype);
910 Object.defineProperty(d, 'baseModelName', {
911 value: this.modelName,
912 configurable: true,
913 writable: false
914 });
915
916 // apply methods and statics
917 applyMethods(d, schema);
918 applyStatics(d, schema);
919
920 if (this[subclassedSymbol] != null) {
921 for (const submodel of this[subclassedSymbol]) {
922 submodel.discriminators = submodel.discriminators || {};
923 submodel.discriminators[name] =
924 model.__subclass(model.db, schema, submodel.collection.name);
925 }
926 }
927
928 return d;
929};
930
931/**
932 * Make sure `this` is a model
933 * @api private
934 */
935
936function _checkContext(ctx, fnName) {
937 // Check context, because it is easy to mistakenly type
938 // `new Model.discriminator()` and get an incomprehensible error
939 if (ctx == null || ctx === global) {
940 throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
941 'model as `this`. Make sure you are calling `MyModel.' + fnName + '()` ' +
942 'where `MyModel` is a Mongoose model.');
943 } else if (ctx[modelSymbol] == null) {
944 throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
945 'model as `this`. Make sure you are not calling ' +
946 '`new Model.' + fnName + '()`');
947 }
948}
949
950// Model (class) features
951
952/*!
953 * Give the constructor the ability to emit events.
954 */
955
956for (const i in EventEmitter.prototype) {
957 Model[i] = EventEmitter.prototype[i];
958}
959
960/**
961 * This function is responsible for initializing the underlying connection in MongoDB based on schema options.
962 * This function performs the following operations:
963 *
964 * - `createCollection()` unless [`autoCreate`](https://mongoosejs.com/docs/guide.html#autoCreate) option is turned off
965 * - `ensureIndexes()` unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) option is turned off
966 * - `createSearchIndex()` on all schema search indexes if `autoSearchIndex` is enabled.
967 *
968 * Mongoose calls this function automatically when a model is a created using
969 * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) or
970 * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()), so you
971 * don't need to call `init()` to trigger index builds.
972 *
973 * However, you _may_ need to call `init()` to get back a promise that will resolve when your indexes are finished.
974 * Calling `await Model.init()` is helpful if you need to wait for indexes to build before continuing.
975 * For example, if you want to wait for unique indexes to build before continuing with a test case.
976 *
977 * #### Example:
978 *
979 * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
980 * // This calls `Event.init()` implicitly, so you don't need to call
981 * // `Event.init()` on your own.
982 * const Event = mongoose.model('Event', eventSchema);
983 *
984 * await Event.init();
985 * console.log('Indexes are done building!');
986 *
987 * @api public
988 * @returns {Promise}
989 */
990
991Model.init = function init() {
992 _checkContext(this, 'init');
993 if (typeof arguments[0] === 'function') {
994 throw new MongooseError('Model.init() no longer accepts a callback');
995 }
996
997 this.schema.emit('init', this);
998
999 if (this.$init != null) {
1000 return this.$init;
1001 }
1002
1003 const conn = this.db;
1004 const _ensureIndexes = async() => {
1005 const autoIndex = utils.getOption(
1006 'autoIndex',
1007 this.schema.options,
1008 conn.config,
1009 conn.base.options
1010 );
1011 if (!autoIndex) {
1012 return;
1013 }
1014 return await this.ensureIndexes({ _automatic: true });
1015 };
1016 const _createSearchIndexes = async() => {
1017 const autoSearchIndex = utils.getOption(
1018 'autoSearchIndex',
1019 this.schema.options,
1020 conn.config,
1021 conn.base.options
1022 );
1023 if (!autoSearchIndex) {
1024 return;
1025 }
1026
1027 const results = [];
1028 for (const searchIndex of this.schema._searchIndexes) {
1029 results.push(await this.createSearchIndex(searchIndex));
1030 }
1031 return results;
1032 };
1033 const _createCollection = async() => {
1034 if ((conn.readyState === STATES.connecting || conn.readyState === STATES.disconnected) && conn._shouldBufferCommands()) {
1035 await new Promise(resolve => {
1036 conn._queue.push({ fn: resolve });
1037 });
1038 }
1039 const autoCreate = utils.getOption(
1040 'autoCreate',
1041 this.schema.options,
1042 conn.config,
1043 conn.base.options
1044 );
1045 if (!autoCreate) {
1046 return;
1047 }
1048 return await this.createCollection();
1049 };
1050
1051 this.$init = _createCollection().
1052 then(() => _ensureIndexes()).
1053 then(() => _createSearchIndexes());
1054
1055 const _catch = this.$init.catch;
1056 const _this = this;
1057 this.$init.catch = function() {
1058 _this.$caught = true;
1059 return _catch.apply(_this.$init, arguments);
1060 };
1061
1062 return this.$init;
1063};
1064
1065
1066/**
1067 * Create the collection for this model. By default, if no indexes are specified,
1068 * mongoose will not create the collection for the model until any documents are
1069 * created. Use this method to create the collection explicitly.
1070 *
1071 * Note 1: You may need to call this before starting a transaction
1072 * See https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
1073 *
1074 * Note 2: You don't have to call this if your schema contains index or unique field.
1075 * In that case, just use `Model.init()`
1076 *
1077 * #### Example:
1078 *
1079 * const userSchema = new Schema({ name: String })
1080 * const User = mongoose.model('User', userSchema);
1081 *
1082 * User.createCollection().then(function(collection) {
1083 * console.log('Collection is created!');
1084 * });
1085 *
1086 * @api public
1087 * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
1088 * @returns {Promise}
1089 */
1090
1091Model.createCollection = async function createCollection(options) {
1092 _checkContext(this, 'createCollection');
1093 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1094 throw new MongooseError('Model.createCollection() no longer accepts a callback');
1095 }
1096
1097 const shouldSkip = await new Promise((resolve, reject) => {
1098 this.hooks.execPre('createCollection', this, [options], (err) => {
1099 if (err != null) {
1100 if (err instanceof Kareem.skipWrappedFunction) {
1101 return resolve(true);
1102 }
1103 return reject(err);
1104 }
1105 resolve();
1106 });
1107 });
1108
1109 const collectionOptions = this &&
1110 this.schema &&
1111 this.schema.options &&
1112 this.schema.options.collectionOptions;
1113 if (collectionOptions != null) {
1114 options = Object.assign({}, collectionOptions, options);
1115 }
1116
1117 const schemaCollation = this &&
1118 this.schema &&
1119 this.schema.options &&
1120 this.schema.options.collation;
1121 if (schemaCollation != null) {
1122 options = Object.assign({ collation: schemaCollation }, options);
1123 }
1124 const capped = this &&
1125 this.schema &&
1126 this.schema.options &&
1127 this.schema.options.capped;
1128 if (capped != null) {
1129 if (typeof capped === 'number') {
1130 options = Object.assign({ capped: true, size: capped }, options);
1131 } else if (typeof capped === 'object') {
1132 options = Object.assign({ capped: true }, capped, options);
1133 }
1134 }
1135 const timeseries = this &&
1136 this.schema &&
1137 this.schema.options &&
1138 this.schema.options.timeseries;
1139 if (timeseries != null) {
1140 options = Object.assign({ timeseries }, options);
1141 if (options.expireAfterSeconds != null) {
1142 // do nothing
1143 } else if (options.expires != null) {
1144 utils.expires(options);
1145 } else if (this.schema.options.expireAfterSeconds != null) {
1146 options.expireAfterSeconds = this.schema.options.expireAfterSeconds;
1147 } else if (this.schema.options.expires != null) {
1148 options.expires = this.schema.options.expires;
1149 utils.expires(options);
1150 }
1151 }
1152
1153 const clusteredIndex = this &&
1154 this.schema &&
1155 this.schema.options &&
1156 this.schema.options.clusteredIndex;
1157 if (clusteredIndex != null) {
1158 options = Object.assign({ clusteredIndex: { ...clusteredIndex, unique: true } }, options);
1159 }
1160
1161 try {
1162 if (!shouldSkip) {
1163 await this.db.createCollection(this.$__collection.collectionName, options);
1164 }
1165 } catch (err) {
1166 if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1167 await new Promise((resolve, reject) => {
1168 const _opts = { error: err };
1169 this.hooks.execPost('createCollection', this, [null], _opts, (err) => {
1170 if (err != null) {
1171 return reject(err);
1172 }
1173 resolve();
1174 });
1175 });
1176 }
1177 }
1178
1179 await new Promise((resolve, reject) => {
1180 this.hooks.execPost('createCollection', this, [this.$__collection], (err) => {
1181 if (err != null) {
1182 return reject(err);
1183 }
1184 resolve();
1185 });
1186 });
1187
1188 return this.$__collection;
1189};
1190
1191/**
1192 * Makes the indexes in MongoDB match the indexes defined in this model's
1193 * schema. This function will drop any indexes that are not defined in
1194 * the model's schema except the `_id` index, and build any indexes that
1195 * are in your schema but not in MongoDB.
1196 *
1197 * See the [introductory blog post](https://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes)
1198 * for more information.
1199 *
1200 * #### Example:
1201 *
1202 * const schema = new Schema({ name: { type: String, unique: true } });
1203 * const Customer = mongoose.model('Customer', schema);
1204 * await Customer.collection.createIndex({ age: 1 }); // Index is not in schema
1205 * // Will drop the 'age' index and create an index on `name`
1206 * await Customer.syncIndexes();
1207 *
1208 * You should be careful about running `syncIndexes()` on production applications under heavy load,
1209 * because index builds are expensive operations, and unexpected index drops can lead to degraded
1210 * performance. Before running `syncIndexes()`, you can use the [`diffIndexes()` function](#Model.diffIndexes())
1211 * to check what indexes `syncIndexes()` will drop and create.
1212 *
1213 * #### Example:
1214 *
1215 * const { toDrop, toCreate } = await Model.diffIndexes();
1216 * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
1217 * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
1218 *
1219 * @param {Object} [options] options to pass to `ensureIndexes()`
1220 * @param {Boolean} [options.background=null] if specified, overrides each index's `background` property
1221 * @return {Promise}
1222 * @api public
1223 */
1224
1225Model.syncIndexes = async function syncIndexes(options) {
1226 _checkContext(this, 'syncIndexes');
1227 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1228 throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1229 }
1230
1231 const model = this;
1232
1233 try {
1234 await model.createCollection();
1235 } catch (err) {
1236 if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1237 throw err;
1238 }
1239 }
1240
1241 const diffIndexesResult = await model.diffIndexes();
1242 const dropped = await model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
1243 await model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
1244
1245 return dropped;
1246};
1247
1248/**
1249 * Create an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
1250 * This function only works when connected to MongoDB Atlas.
1251 *
1252 * #### Example:
1253 *
1254 * const schema = new Schema({ name: { type: String, unique: true } });
1255 * const Customer = mongoose.model('Customer', schema);
1256 * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
1257 *
1258 * @param {Object} description index options, including `name` and `definition`
1259 * @param {String} description.name
1260 * @param {Object} description.definition
1261 * @return {Promise}
1262 * @api public
1263 */
1264
1265Model.createSearchIndex = async function createSearchIndex(description) {
1266 _checkContext(this, 'createSearchIndex');
1267
1268 return await this.$__collection.createSearchIndex(description);
1269};
1270
1271/**
1272 * Update an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
1273 * This function only works when connected to MongoDB Atlas.
1274 *
1275 * #### Example:
1276 *
1277 * const schema = new Schema({ name: { type: String, unique: true } });
1278 * const Customer = mongoose.model('Customer', schema);
1279 * await Customer.updateSearchIndex('test', { mappings: { dynamic: true } });
1280 *
1281 * @param {String} name
1282 * @param {Object} definition
1283 * @return {Promise}
1284 * @api public
1285 */
1286
1287Model.updateSearchIndex = async function updateSearchIndex(name, definition) {
1288 _checkContext(this, 'updateSearchIndex');
1289
1290 return await this.$__collection.updateSearchIndex(name, definition);
1291};
1292
1293/**
1294 * Delete an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) by name.
1295 * This function only works when connected to MongoDB Atlas.
1296 *
1297 * #### Example:
1298 *
1299 * const schema = new Schema({ name: { type: String, unique: true } });
1300 * const Customer = mongoose.model('Customer', schema);
1301 * await Customer.dropSearchIndex('test');
1302 *
1303 * @param {String} name
1304 * @return {Promise}
1305 * @api public
1306 */
1307
1308Model.dropSearchIndex = async function dropSearchIndex(name) {
1309 _checkContext(this, 'dropSearchIndex');
1310
1311 return await this.$__collection.dropSearchIndex(name);
1312};
1313
1314/**
1315 * List all [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) on this model's collection.
1316 * This function only works when connected to MongoDB Atlas.
1317 *
1318 * #### Example:
1319 *
1320 * const schema = new Schema({ name: { type: String, unique: true } });
1321 * const Customer = mongoose.model('Customer', schema);
1322 *
1323 * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
1324 * const res = await Customer.listSearchIndexes(); // Includes `[{ name: 'test' }]`
1325 *
1326 * @param {Object} [options]
1327 * @return {Promise<Array>}
1328 * @api public
1329 */
1330
1331Model.listSearchIndexes = async function listSearchIndexes(options) {
1332 _checkContext(this, 'listSearchIndexes');
1333
1334 const cursor = await this.$__collection.listSearchIndexes(options);
1335
1336 return await cursor.toArray();
1337};
1338
1339/**
1340 * Does a dry-run of `Model.syncIndexes()`, returning the indexes that `syncIndexes()` would drop and create if you were to run `syncIndexes()`.
1341 *
1342 * #### Example:
1343 *
1344 * const { toDrop, toCreate } = await Model.diffIndexes();
1345 * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
1346 * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
1347 *
1348 * @param {Object} [options]
1349 * @return {Promise<Object>} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`.
1350 */
1351
1352Model.diffIndexes = async function diffIndexes() {
1353 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1354 throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1355 }
1356
1357 const model = this;
1358
1359 let dbIndexes = await model.listIndexes().catch(err => {
1360 if (err.codeName == 'NamespaceNotFound') {
1361 return undefined;
1362 }
1363 throw err;
1364 });
1365 if (dbIndexes === undefined) {
1366 dbIndexes = [];
1367 }
1368 dbIndexes = getRelatedDBIndexes(model, dbIndexes);
1369
1370 const schema = model.schema;
1371 const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
1372
1373 const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
1374 const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
1375
1376 return { toDrop, toCreate };
1377};
1378
1379function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
1380 const toCreate = [];
1381
1382 for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
1383 let found = false;
1384
1385 const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
1386
1387 for (const index of dbIndexes) {
1388 if (isDefaultIdIndex(index)) {
1389 continue;
1390 }
1391 if (
1392 isIndexEqual(schemaIndexKeysObject, options, index) &&
1393 !toDrop.includes(index.name)
1394 ) {
1395 found = true;
1396 break;
1397 }
1398 }
1399
1400 if (!found) {
1401 toCreate.push(schemaIndexKeysObject);
1402 }
1403 }
1404
1405 return toCreate;
1406}
1407
1408function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
1409 const toDrop = [];
1410
1411 for (const dbIndex of dbIndexes) {
1412 let found = false;
1413 // Never try to drop `_id` index, MongoDB server doesn't allow it
1414 if (isDefaultIdIndex(dbIndex)) {
1415 continue;
1416 }
1417
1418 for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
1419 const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
1420 applySchemaCollation(schemaIndexKeysObject, options, schema.options);
1421
1422 if (isIndexEqual(schemaIndexKeysObject, options, dbIndex)) {
1423 found = true;
1424 break;
1425 }
1426 }
1427
1428 if (!found) {
1429 toDrop.push(dbIndex.name);
1430 }
1431 }
1432
1433 return toDrop;
1434}
1435/**
1436 * Deletes all indexes that aren't defined in this model's schema. Used by
1437 * `syncIndexes()`.
1438 *
1439 * The returned promise resolves to a list of the dropped indexes' names as an array
1440 *
1441 * @param {Function} [callback] optional callback
1442 * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback.
1443 * @api public
1444 */
1445
1446Model.cleanIndexes = async function cleanIndexes(options) {
1447 _checkContext(this, 'cleanIndexes');
1448 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1449 throw new MongooseError('Model.cleanIndexes() no longer accepts a callback');
1450 }
1451 const model = this;
1452
1453 const collection = model.$__collection;
1454
1455 if (Array.isArray(options && options.toDrop)) {
1456 const res = await _dropIndexes(options.toDrop, collection);
1457 return res;
1458 }
1459
1460 const res = await model.diffIndexes();
1461 return await _dropIndexes(res.toDrop, collection);
1462};
1463
1464async function _dropIndexes(toDrop, collection) {
1465 if (toDrop.length === 0) {
1466 return [];
1467 }
1468
1469 await Promise.all(toDrop.map(indexName => collection.dropIndex(indexName)));
1470 return toDrop;
1471}
1472
1473/**
1474 * Lists the indexes currently defined in MongoDB. This may or may not be
1475 * the same as the indexes defined in your schema depending on whether you
1476 * use the [`autoIndex` option](https://mongoosejs.com/docs/guide.html#autoIndex) and if you
1477 * build indexes manually.
1478 *
1479 * @return {Promise}
1480 * @api public
1481 */
1482
1483Model.listIndexes = async function listIndexes() {
1484 _checkContext(this, 'listIndexes');
1485 if (typeof arguments[0] === 'function') {
1486 throw new MongooseError('Model.listIndexes() no longer accepts a callback');
1487 }
1488
1489 if (this.$__collection.buffer) {
1490 await new Promise(resolve => {
1491 this.$__collection.addQueue(resolve);
1492 });
1493 }
1494
1495 return this.$__collection.listIndexes().toArray();
1496};
1497
1498/**
1499 * Sends `createIndex` commands to mongo for each index declared in the schema.
1500 * The `createIndex` commands are sent in series.
1501 *
1502 * #### Example:
1503 *
1504 * await Event.ensureIndexes();
1505 *
1506 * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
1507 *
1508 * #### Example:
1509 *
1510 * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
1511 * const Event = mongoose.model('Event', eventSchema);
1512 *
1513 * Event.on('index', function (err) {
1514 * if (err) console.error(err); // error occurred during index creation
1515 * });
1516 *
1517 * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
1518 *
1519 * @param {Object} [options] internal options
1520 * @return {Promise}
1521 * @api public
1522 */
1523
1524Model.ensureIndexes = async function ensureIndexes(options) {
1525 _checkContext(this, 'ensureIndexes');
1526 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1527 throw new MongooseError('Model.ensureIndexes() no longer accepts a callback');
1528 }
1529
1530 await new Promise((resolve, reject) => {
1531 _ensureIndexes(this, options, (err) => {
1532 if (err != null) {
1533 return reject(err);
1534 }
1535 resolve();
1536 });
1537 });
1538};
1539
1540/**
1541 * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createIndex)
1542 * function.
1543 *
1544 * @param {Object} [options] internal options
1545 * @return {Promise}
1546 * @api public
1547 */
1548
1549Model.createIndexes = async function createIndexes(options) {
1550 _checkContext(this, 'createIndexes');
1551
1552 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1553 throw new MongooseError('Model.createIndexes() no longer accepts a callback');
1554 }
1555
1556 return this.ensureIndexes(options);
1557};
1558
1559
1560/*!
1561 * ignore
1562 */
1563
1564function _ensureIndexes(model, options, callback) {
1565 const indexes = model.schema.indexes();
1566 let indexError;
1567
1568 options = options || {};
1569 const done = function(err) {
1570 if (err && !model.$caught) {
1571 model.emit('error', err);
1572 }
1573 model.emit('index', err || indexError);
1574 callback && callback(err || indexError);
1575 };
1576
1577 for (const index of indexes) {
1578 if (isDefaultIdIndex(index)) {
1579 utils.warn('mongoose: Cannot specify a custom index on `_id` for ' +
1580 'model name "' + model.modelName + '", ' +
1581 'MongoDB does not allow overwriting the default `_id` index. See ' +
1582 'https://bit.ly/mongodb-id-index');
1583 }
1584 }
1585
1586 if (!indexes.length) {
1587 immediate(function() {
1588 done();
1589 });
1590 return;
1591 }
1592 // Indexes are created one-by-one to support how MongoDB < 2.4 deals
1593 // with background indexes.
1594
1595 const indexSingleDone = function(err, fields, options, name) {
1596 model.emit('index-single-done', err, fields, options, name);
1597 };
1598 const indexSingleStart = function(fields, options) {
1599 model.emit('index-single-start', fields, options);
1600 };
1601
1602 const baseSchema = model.schema._baseSchema;
1603 const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : [];
1604
1605 immediate(function() {
1606 // If buffering is off, do this manually.
1607 if (options._automatic && !model.collection.collection) {
1608 model.collection.addQueue(create, []);
1609 } else {
1610 create();
1611 }
1612 });
1613
1614
1615 function create() {
1616 if (options._automatic) {
1617 if (model.schema.options.autoIndex === false ||
1618 (model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
1619 return done();
1620 }
1621 }
1622
1623 const index = indexes.shift();
1624 if (!index) {
1625 return done();
1626 }
1627 if (options._automatic && index[1]._autoIndex === false) {
1628 return create();
1629 }
1630
1631 if (baseSchemaIndexes.find(i => utils.deepEqual(i, index))) {
1632 return create();
1633 }
1634
1635 const indexFields = clone(index[0]);
1636 const indexOptions = clone(index[1]);
1637
1638 delete indexOptions._autoIndex;
1639 decorateDiscriminatorIndexOptions(model.schema, indexOptions);
1640 applyWriteConcern(model.schema, indexOptions);
1641 applySchemaCollation(indexFields, indexOptions, model.schema.options);
1642
1643 indexSingleStart(indexFields, options);
1644
1645 if ('background' in options) {
1646 indexOptions.background = options.background;
1647 }
1648
1649 if ('toCreate' in options) {
1650 if (options.toCreate.length === 0) {
1651 return done();
1652 }
1653 }
1654
1655 model.collection.createIndex(indexFields, indexOptions).then(
1656 name => {
1657 indexSingleDone(null, indexFields, indexOptions, name);
1658 create();
1659 },
1660 err => {
1661 if (!indexError) {
1662 indexError = err;
1663 }
1664 if (!model.$caught) {
1665 model.emit('error', err);
1666 }
1667
1668 indexSingleDone(err, indexFields, indexOptions);
1669 create();
1670 }
1671 );
1672 }
1673}
1674
1675/**
1676 * Schema the model uses.
1677 *
1678 * @property schema
1679 * @static
1680 * @api public
1681 * @memberOf Model
1682 */
1683
1684Model.schema;
1685
1686/**
1687 * Connection instance the model uses.
1688 *
1689 * @property db
1690 * @static
1691 * @api public
1692 * @memberOf Model
1693 */
1694
1695Model.db;
1696
1697/**
1698 * Collection the model uses.
1699 *
1700 * @property collection
1701 * @api public
1702 * @memberOf Model
1703 */
1704
1705Model.collection;
1706
1707/**
1708 * Internal collection the model uses.
1709 *
1710 * @property collection
1711 * @api private
1712 * @memberOf Model
1713 */
1714Model.$__collection;
1715
1716/**
1717 * Base Mongoose instance the model uses.
1718 *
1719 * @property base
1720 * @api public
1721 * @memberOf Model
1722 */
1723
1724Model.base;
1725
1726/**
1727 * Registered discriminators for this model.
1728 *
1729 * @property discriminators
1730 * @api public
1731 * @memberOf Model
1732 */
1733
1734Model.discriminators;
1735
1736/**
1737 * Translate any aliases fields/conditions so the final query or document object is pure
1738 *
1739 * #### Example:
1740 *
1741 * await Character.find(Character.translateAliases({
1742 * '名': 'Eddard Stark' // Alias for 'name'
1743 * });
1744 *
1745 * By default, `translateAliases()` overwrites raw fields with aliased fields.
1746 * So if `n` is an alias for `name`, `{ n: 'alias', name: 'raw' }` will resolve to `{ name: 'alias' }`.
1747 * However, you can set the `errorOnDuplicates` option to throw an error if there are potentially conflicting paths.
1748 * The `translateAliases` option for queries uses `errorOnDuplicates`.
1749 *
1750 * #### Note:
1751 *
1752 * Only translate arguments of object type anything else is returned raw
1753 *
1754 * @param {Object} fields fields/conditions that may contain aliased keys
1755 * @param {Boolean} [errorOnDuplicates] if true, throw an error if there's both a key and an alias for that key in `fields`
1756 * @return {Object} the translated 'pure' fields/conditions
1757 */
1758Model.translateAliases = function translateAliases(fields, errorOnDuplicates) {
1759 _checkContext(this, 'translateAliases');
1760
1761 const translate = (key, value) => {
1762 let alias;
1763 const translated = [];
1764 const fieldKeys = key.split('.');
1765 let currentSchema = this.schema;
1766 for (const i in fieldKeys) {
1767 const name = fieldKeys[i];
1768 if (currentSchema && currentSchema.aliases[name]) {
1769 alias = currentSchema.aliases[name];
1770 if (errorOnDuplicates && alias in fields) {
1771 throw new MongooseError(`Provided object has both field "${name}" and its alias "${alias}"`);
1772 }
1773 // Alias found,
1774 translated.push(alias);
1775 } else {
1776 alias = name;
1777 // Alias not found, so treat as un-aliased key
1778 translated.push(name);
1779 }
1780
1781 // Check if aliased path is a schema
1782 if (currentSchema && currentSchema.paths[alias]) {
1783 currentSchema = currentSchema.paths[alias].schema;
1784 }
1785 else
1786 currentSchema = null;
1787 }
1788
1789 const translatedKey = translated.join('.');
1790 if (fields instanceof Map)
1791 fields.set(translatedKey, value);
1792 else
1793 fields[translatedKey] = value;
1794
1795 if (translatedKey !== key) {
1796 // We'll be using the translated key instead
1797 if (fields instanceof Map) {
1798 // Delete from map
1799 fields.delete(key);
1800 } else {
1801 // Delete from object
1802 delete fields[key]; // We'll be using the translated key instead
1803 }
1804 }
1805 return fields;
1806 };
1807
1808 if (typeof fields === 'object') {
1809 // Fields is an object (query conditions or document fields)
1810 if (fields instanceof Map) {
1811 // A Map was supplied
1812 for (const field of new Map(fields)) {
1813 fields = translate(field[0], field[1]);
1814 }
1815 } else {
1816 // Infer a regular object was supplied
1817 for (const key of Object.keys(fields)) {
1818 fields = translate(key, fields[key]);
1819 if (key[0] === '$') {
1820 if (Array.isArray(fields[key])) {
1821 for (const i in fields[key]) {
1822 // Recursively translate nested queries
1823 fields[key][i] = this.translateAliases(fields[key][i]);
1824 }
1825 } else {
1826 this.translateAliases(fields[key]);
1827 }
1828 }
1829 }
1830 }
1831
1832 return fields;
1833 } else {
1834 // Don't know typeof fields
1835 return fields;
1836 }
1837};
1838
1839/**
1840 * Deletes the first document that matches `conditions` from the collection.
1841 * It returns an object with the property `deletedCount` indicating how many documents were deleted.
1842 * Behaves like `remove()`, but deletes at most one document regardless of the
1843 * `single` option.
1844 *
1845 * #### Example:
1846 *
1847 * await Character.deleteOne({ name: 'Eddard Stark' }); // returns {deletedCount: 1}
1848 *
1849 * #### Note:
1850 *
1851 * This function triggers `deleteOne` query hooks. Read the
1852 * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
1853 *
1854 * @param {Object} conditions
1855 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
1856 * @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.
1857 * @return {Query}
1858 * @api public
1859 */
1860
1861Model.deleteOne = function deleteOne(conditions, options) {
1862 _checkContext(this, 'deleteOne');
1863
1864 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
1865 throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
1866 }
1867
1868 const mq = new this.Query({}, {}, this, this.$__collection);
1869 mq.setOptions(options);
1870
1871 return mq.deleteOne(conditions);
1872};
1873
1874/**
1875 * Deletes all of the documents that match `conditions` from the collection.
1876 * It returns an object with the property `deletedCount` containing the number of documents deleted.
1877 * Behaves like `remove()`, but deletes all documents that match `conditions`
1878 * regardless of the `single` option.
1879 *
1880 * #### Example:
1881 *
1882 * await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); // returns {deletedCount: x} where x is the number of documents deleted.
1883 *
1884 * #### Note:
1885 *
1886 * This function triggers `deleteMany` query hooks. Read the
1887 * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
1888 *
1889 * @param {Object} conditions
1890 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
1891 * @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.
1892 * @return {Query}
1893 * @api public
1894 */
1895
1896Model.deleteMany = function deleteMany(conditions, options) {
1897 _checkContext(this, 'deleteMany');
1898
1899 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
1900 throw new MongooseError('Model.deleteMany() no longer accepts a callback');
1901 }
1902
1903 const mq = new this.Query({}, {}, this, this.$__collection);
1904 mq.setOptions(options);
1905
1906 return mq.deleteMany(conditions);
1907};
1908
1909/**
1910 * Finds documents.
1911 *
1912 * Mongoose casts the `filter` to match the model's schema before the command is sent.
1913 * See our [query casting tutorial](https://mongoosejs.com/docs/tutorials/query_casting.html) for
1914 * more information on how Mongoose casts `filter`.
1915 *
1916 * #### Example:
1917 *
1918 * // find all documents
1919 * await MyModel.find({});
1920 *
1921 * // find all documents named john and at least 18
1922 * await MyModel.find({ name: 'john', age: { $gte: 18 } }).exec();
1923 *
1924 * // executes, name LIKE john and only selecting the "name" and "friends" fields
1925 * await MyModel.find({ name: /john/i }, 'name friends').exec();
1926 *
1927 * // passing options
1928 * await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec();
1929 *
1930 * @param {Object|ObjectId} filter
1931 * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
1932 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
1933 * @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.
1934 * @return {Query}
1935 * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
1936 * @see query casting https://mongoosejs.com/docs/tutorials/query_casting.html
1937 * @api public
1938 */
1939
1940Model.find = function find(conditions, projection, options) {
1941 _checkContext(this, 'find');
1942 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
1943 throw new MongooseError('Model.find() no longer accepts a callback');
1944 }
1945
1946 const mq = new this.Query({}, {}, this, this.$__collection);
1947 mq.select(projection);
1948 mq.setOptions(options);
1949
1950 return mq.find(conditions);
1951};
1952
1953/**
1954 * Finds a single document by its _id field. `findById(id)` is almost*
1955 * equivalent to `findOne({ _id: id })`. If you want to query by a document's
1956 * `_id`, use `findById()` instead of `findOne()`.
1957 *
1958 * The `id` is cast based on the Schema before sending the command.
1959 *
1960 * This function triggers the following middleware.
1961 *
1962 * - `findOne()`
1963 *
1964 * \* Except for how it treats `undefined`. If you use `findOne()`, you'll see
1965 * that `findOne(undefined)` and `findOne({ _id: undefined })` are equivalent
1966 * to `findOne({})` and return arbitrary documents. However, mongoose
1967 * translates `findById(undefined)` into `findOne({ _id: null })`.
1968 *
1969 * #### Example:
1970 *
1971 * // Find the adventure with the given `id`, or `null` if not found
1972 * await Adventure.findById(id).exec();
1973 *
1974 * // select only the adventures name and length
1975 * await Adventure.findById(id, 'name length').exec();
1976 *
1977 * @param {Any} id value of `_id` to query by
1978 * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
1979 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
1980 * @return {Query}
1981 * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
1982 * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
1983 * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id
1984 * @api public
1985 */
1986
1987Model.findById = function findById(id, projection, options) {
1988 _checkContext(this, 'findById');
1989 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
1990 throw new MongooseError('Model.findById() no longer accepts a callback');
1991 }
1992
1993 if (typeof id === 'undefined') {
1994 id = null;
1995 }
1996
1997 return this.findOne({ _id: id }, projection, options);
1998};
1999
2000/**
2001 * Finds one document.
2002 *
2003 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
2004 *
2005 * *Note:* `conditions` is optional, and if `conditions` is null or undefined,
2006 * mongoose will send an empty `findOne` command to MongoDB, which will return
2007 * an arbitrary document. If you're querying by `_id`, use `findById()` instead.
2008 *
2009 * #### Example:
2010 *
2011 * // Find one adventure whose `country` is 'Croatia', otherwise `null`
2012 * await Adventure.findOne({ country: 'Croatia' }).exec();
2013 *
2014 * // Model.findOne() no longer accepts a callback
2015 *
2016 * // Select only the adventures name and length
2017 * await Adventure.findOne({ country: 'Croatia' }, 'name length').exec();
2018 *
2019 * @param {Object} [conditions]
2020 * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
2021 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2022 * @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.
2023 * @return {Query}
2024 * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
2025 * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
2026 * @api public
2027 */
2028
2029Model.findOne = function findOne(conditions, projection, options) {
2030 _checkContext(this, 'findOne');
2031 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
2032 throw new MongooseError('Model.findOne() no longer accepts a callback');
2033 }
2034
2035 const mq = new this.Query({}, {}, this, this.$__collection);
2036 mq.select(projection);
2037 mq.setOptions(options);
2038
2039 return mq.findOne(conditions);
2040};
2041
2042/**
2043 * Estimates the number of documents in the MongoDB collection. Faster than
2044 * using `countDocuments()` for large collections because
2045 * `estimatedDocumentCount()` uses collection metadata rather than scanning
2046 * the entire collection.
2047 *
2048 * #### Example:
2049 *
2050 * const numAdventures = await Adventure.estimatedDocumentCount();
2051 *
2052 * @param {Object} [options]
2053 * @return {Query}
2054 * @api public
2055 */
2056
2057Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
2058 _checkContext(this, 'estimatedDocumentCount');
2059
2060 const mq = new this.Query({}, {}, this, this.$__collection);
2061
2062 return mq.estimatedDocumentCount(options);
2063};
2064
2065/**
2066 * Counts number of documents matching `filter` in a database collection.
2067 *
2068 * #### Example:
2069 *
2070 * Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
2071 * console.log('there are %d jungle adventures', count);
2072 * });
2073 *
2074 * If you want to count all documents in a large collection,
2075 * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
2076 * instead. If you call `countDocuments({})`, MongoDB will always execute
2077 * a full collection scan and **not** use any indexes.
2078 *
2079 * The `countDocuments()` function is similar to `count()`, but there are a
2080 * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#countDocuments).
2081 * Below are the operators that `count()` supports but `countDocuments()` does not,
2082 * and the suggested replacement:
2083 *
2084 * - `$where`: [`$expr`](https://www.mongodb.com/docs/manual/reference/operator/query/expr/)
2085 * - `$near`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$center`](https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center)
2086 * - `$nearSphere`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
2087 *
2088 * @param {Object} filter
2089 * @return {Query}
2090 * @api public
2091 */
2092
2093Model.countDocuments = function countDocuments(conditions, options) {
2094 _checkContext(this, 'countDocuments');
2095 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
2096 throw new MongooseError('Model.countDocuments() no longer accepts a callback');
2097 }
2098
2099 const mq = new this.Query({}, {}, this, this.$__collection);
2100 if (options != null) {
2101 mq.setOptions(options);
2102 }
2103
2104 return mq.countDocuments(conditions);
2105};
2106
2107
2108/**
2109 * Creates a Query for a `distinct` operation.
2110 *
2111 * #### Example:
2112 *
2113 * const query = Link.distinct('url');
2114 * query.exec();
2115 *
2116 * @param {String} field
2117 * @param {Object} [conditions] optional
2118 * @return {Query}
2119 * @api public
2120 */
2121
2122Model.distinct = function distinct(field, conditions) {
2123 _checkContext(this, 'distinct');
2124 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
2125 throw new MongooseError('Model.distinct() no longer accepts a callback');
2126 }
2127
2128 const mq = new this.Query({}, {}, this, this.$__collection);
2129
2130 return mq.distinct(field, conditions);
2131};
2132
2133/**
2134 * Creates a Query, applies the passed conditions, and returns the Query.
2135 *
2136 * For example, instead of writing:
2137 *
2138 * User.find({ age: { $gte: 21, $lte: 65 } });
2139 *
2140 * we can instead write:
2141 *
2142 * User.where('age').gte(21).lte(65).exec();
2143 *
2144 * Since the Query class also supports `where` you can continue chaining
2145 *
2146 * User
2147 * .where('age').gte(21).lte(65)
2148 * .where('name', /^b/i)
2149 * ... etc
2150 *
2151 * @param {String} path
2152 * @param {Object} [val] optional value
2153 * @return {Query}
2154 * @api public
2155 */
2156
2157Model.where = function where(path, val) {
2158 _checkContext(this, 'where');
2159
2160 void val; // eslint
2161 const mq = new this.Query({}, {}, this, this.$__collection).find({});
2162 return mq.where.apply(mq, arguments);
2163};
2164
2165/**
2166 * Creates a `Query` and specifies a `$where` condition.
2167 *
2168 * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
2169 *
2170 * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
2171 *
2172 * @param {String|Function} argument is a javascript string or anonymous function
2173 * @method $where
2174 * @memberOf Model
2175 * @return {Query}
2176 * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where
2177 * @api public
2178 */
2179
2180Model.$where = function $where() {
2181 _checkContext(this, '$where');
2182
2183 const mq = new this.Query({}, {}, this, this.$__collection).find({});
2184 return mq.$where.apply(mq, arguments);
2185};
2186
2187/**
2188 * Issues a mongodb findOneAndUpdate command.
2189 *
2190 * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes if `callback` is passed else a Query object is returned.
2191 *
2192 * #### Example:
2193 *
2194 * A.findOneAndUpdate(conditions, update, options) // returns Query
2195 * A.findOneAndUpdate(conditions, update) // returns Query
2196 * A.findOneAndUpdate() // returns Query
2197 *
2198 * #### Note:
2199 *
2200 * All top level update keys which are not `atomic` operation names are treated as set operations:
2201 *
2202 * #### Example:
2203 *
2204 * const query = { name: 'borne' };
2205 * Model.findOneAndUpdate(query, { name: 'jason bourne' }, options)
2206 *
2207 * // is sent as
2208 * Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options)
2209 *
2210 * #### Note:
2211 *
2212 * `findOneAndX` and `findByIdAndX` functions support limited validation that
2213 * you can enable by setting the `runValidators` option.
2214 *
2215 * If you need full-fledged validation, use the traditional approach of first
2216 * retrieving the document.
2217 *
2218 * const doc = await Model.findById(id);
2219 * doc.name = 'jason bourne';
2220 * await doc.save();
2221 *
2222 * @param {Object} [conditions]
2223 * @param {Object} [update]
2224 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2225 * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2226 * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2227 * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2228 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2229 * @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.
2230 * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
2231 * @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())
2232 * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
2233 * @param {Object|String} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
2234 * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
2235 * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
2236 * @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
2237 * @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
2238 * @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)
2239 * @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.
2240 * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
2241 * @return {Query}
2242 * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
2243 * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
2244 * @api public
2245 */
2246
2247Model.findOneAndUpdate = function(conditions, update, options) {
2248 _checkContext(this, 'findOneAndUpdate');
2249 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
2250 throw new MongooseError('Model.findOneAndUpdate() no longer accepts a callback');
2251 }
2252
2253 if (arguments.length === 1) {
2254 update = conditions;
2255 conditions = null;
2256 options = null;
2257 }
2258
2259 let fields;
2260 if (options) {
2261 fields = options.fields || options.projection;
2262 }
2263
2264 update = clone(update, {
2265 depopulate: true,
2266 _isNested: true
2267 });
2268
2269 decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
2270
2271 const mq = new this.Query({}, {}, this, this.$__collection);
2272 mq.select(fields);
2273
2274 return mq.findOneAndUpdate(conditions, update, options);
2275};
2276
2277/**
2278 * Issues a mongodb findOneAndUpdate command by a document's _id field.
2279 * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
2280 *
2281 * Finds a matching document, updates it according to the `update` arg,
2282 * passing any `options`, and returns the found document (if any).
2283 *
2284 * This function triggers the following middleware.
2285 *
2286 * - `findOneAndUpdate()`
2287 *
2288 * #### Example:
2289 *
2290 * A.findByIdAndUpdate(id, update, options) // returns Query
2291 * A.findByIdAndUpdate(id, update) // returns Query
2292 * A.findByIdAndUpdate() // returns Query
2293 *
2294 * #### Note:
2295 *
2296 * All top level update keys which are not `atomic` operation names are treated as set operations:
2297 *
2298 * #### Example:
2299 *
2300 * Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options)
2301 *
2302 * // is sent as
2303 * Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options)
2304 *
2305 * #### Note:
2306 *
2307 * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
2308 * enable validation by setting the `runValidators` option.
2309 *
2310 * If you need full-fledged validation, use the traditional approach of first
2311 * retrieving the document.
2312 *
2313 * const doc = await Model.findById(id)
2314 * doc.name = 'jason bourne';
2315 * await doc.save();
2316 *
2317 * @param {Object|Number|String} id value of `_id` to query by
2318 * @param {Object} [update]
2319 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2320 * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2321 * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2322 * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2323 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2324 * @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.
2325 * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
2326 * @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
2327 * @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
2328 * @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
2329 * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
2330 * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
2331 * @param {Object|String} [options.select] sets the document fields to return.
2332 * @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.
2333 * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
2334 * @return {Query}
2335 * @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
2336 * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
2337 * @api public
2338 */
2339
2340Model.findByIdAndUpdate = function(id, update, options) {
2341 _checkContext(this, 'findByIdAndUpdate');
2342 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
2343 throw new MongooseError('Model.findByIdAndUpdate() no longer accepts a callback');
2344 }
2345
2346 // if a model is passed in instead of an id
2347 if (id instanceof Document) {
2348 id = id._id;
2349 }
2350
2351 return this.findOneAndUpdate.call(this, { _id: id }, update, options);
2352};
2353
2354/**
2355 * Issue a MongoDB `findOneAndDelete()` command.
2356 *
2357 * Finds a matching document, removes it, and returns the found document (if any).
2358 *
2359 * This function triggers the following middleware.
2360 *
2361 * - `findOneAndDelete()`
2362 *
2363 * #### Example:
2364 *
2365 * A.findOneAndDelete(conditions, options) // return Query
2366 * A.findOneAndDelete(conditions) // returns Query
2367 * A.findOneAndDelete() // returns Query
2368 *
2369 * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
2370 * enable validation by setting the `runValidators` option.
2371 *
2372 * If you need full-fledged validation, use the traditional approach of first
2373 * retrieving the document.
2374 *
2375 * const doc = await Model.findById(id)
2376 * doc.name = 'jason bourne';
2377 * await doc.save();
2378 *
2379 * @param {Object} conditions
2380 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2381 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2382 * @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())
2383 * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2384 * @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
2385 * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
2386 * @param {Object|String} [options.select] sets the document fields to return.
2387 * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
2388 * @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.
2389 * @return {Query}
2390 * @api public
2391 */
2392
2393Model.findOneAndDelete = function(conditions, options) {
2394 _checkContext(this, 'findOneAndDelete');
2395
2396 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
2397 throw new MongooseError('Model.findOneAndDelete() no longer accepts a callback');
2398 }
2399
2400 let fields;
2401 if (options) {
2402 fields = options.select;
2403 options.select = undefined;
2404 }
2405
2406 const mq = new this.Query({}, {}, this, this.$__collection);
2407 mq.select(fields);
2408
2409 return mq.findOneAndDelete(conditions, options);
2410};
2411
2412/**
2413 * Issue a MongoDB `findOneAndDelete()` command by a document's _id field.
2414 * In other words, `findByIdAndDelete(id)` is a shorthand for
2415 * `findOneAndDelete({ _id: id })`.
2416 *
2417 * This function triggers the following middleware.
2418 *
2419 * - `findOneAndDelete()`
2420 *
2421 * @param {Object|Number|String} id value of `_id` to query by
2422 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2423 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2424 * @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.
2425 * @return {Query}
2426 * @see Model.findOneAndDelete https://mongoosejs.com/docs/api/model.html#Model.findOneAndDelete()
2427 * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
2428 */
2429
2430Model.findByIdAndDelete = function(id, options) {
2431 _checkContext(this, 'findByIdAndDelete');
2432
2433 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
2434 throw new MongooseError('Model.findByIdAndDelete() no longer accepts a callback');
2435 }
2436
2437 return this.findOneAndDelete({ _id: id }, options);
2438};
2439
2440/**
2441 * Issue a MongoDB `findOneAndReplace()` command.
2442 *
2443 * Finds a matching document, replaces it with the provided doc, and returns the document.
2444 *
2445 * This function triggers the following query middleware.
2446 *
2447 * - `findOneAndReplace()`
2448 *
2449 * #### Example:
2450 *
2451 * A.findOneAndReplace(filter, replacement, options) // return Query
2452 * A.findOneAndReplace(filter, replacement) // returns Query
2453 * A.findOneAndReplace() // returns Query
2454 *
2455 * @param {Object} filter Replace the first document that matches this filter
2456 * @param {Object} [replacement] Replace with this document
2457 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2458 * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2459 * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2460 * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2461 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2462 * @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.
2463 * @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())
2464 * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
2465 * @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
2466 * @param {Object|String} [options.select] sets the document fields to return.
2467 * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
2468 * @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.
2469 * @return {Query}
2470 * @api public
2471 */
2472
2473Model.findOneAndReplace = function(filter, replacement, options) {
2474 _checkContext(this, 'findOneAndReplace');
2475
2476 if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
2477 throw new MongooseError('Model.findOneAndReplace() no longer accepts a callback');
2478 }
2479
2480 let fields;
2481 if (options) {
2482 fields = options.select;
2483 options.select = undefined;
2484 }
2485
2486 const mq = new this.Query({}, {}, this, this.$__collection);
2487 mq.select(fields);
2488
2489 return mq.findOneAndReplace(filter, replacement, options);
2490};
2491
2492/**
2493 * Shortcut for saving one or more documents to the database.
2494 * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in
2495 * docs.
2496 *
2497 * This function triggers the following middleware.
2498 *
2499 * - `save()`
2500 *
2501 * #### Example:
2502 *
2503 * // Insert one new `Character` document
2504 * await Character.create({ name: 'Jean-Luc Picard' });
2505 *
2506 * // Insert multiple new `Character` documents
2507 * await Character.create([{ name: 'Will Riker' }, { name: 'Geordi LaForge' }]);
2508 *
2509 * // Create a new character within a transaction. Note that you **must**
2510 * // pass an array as the first parameter to `create()` if you want to
2511 * // specify options.
2512 * await Character.create([{ name: 'Jean-Luc Picard' }], { session });
2513 *
2514 * @param {Array|Object} docs Documents to insert, as a spread or array
2515 * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options.
2516 * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
2517 * @param {Boolean} [options.aggregateErrors] Aggregate Errors instead of throwing the first one that occurs. Default: false
2518 * @return {Promise}
2519 * @api public
2520 */
2521
2522Model.create = async function create(doc, options) {
2523 if (typeof options === 'function' ||
2524 typeof arguments[2] === 'function') {
2525 throw new MongooseError('Model.create() no longer accepts a callback');
2526 }
2527
2528 _checkContext(this, 'create');
2529
2530 let args;
2531 const discriminatorKey = this.schema.options.discriminatorKey;
2532
2533 if (Array.isArray(doc)) {
2534 args = doc;
2535 options = options != null && typeof options === 'object' ? options : {};
2536 } else {
2537 const last = arguments[arguments.length - 1];
2538 options = {};
2539 const hasCallback = typeof last === 'function' ||
2540 typeof options === 'function' ||
2541 typeof arguments[2] === 'function';
2542 if (hasCallback) {
2543 throw new MongooseError('Model.create() no longer accepts a callback');
2544 } else {
2545 args = [...arguments];
2546 // For backwards compatibility with 6.x, because of gh-5061 Mongoose 6.x and
2547 // older would treat a falsy last arg as a callback. We don't want to throw
2548 // an error here, because it would look strange if `Test.create({}, void 0)`
2549 // threw a callback error. But we also don't want to create an unnecessary document.
2550 if (args.length > 1 && !last) {
2551 args.pop();
2552 }
2553 }
2554
2555 if (args.length === 2 &&
2556 args[0] != null &&
2557 args[1] != null &&
2558 args[0].session == null &&
2559 last &&
2560 getConstructorName(last.session) === 'ClientSession' &&
2561 !this.schema.path('session')) {
2562 // Probably means the user is running into the common mistake of trying
2563 // to use a spread to specify options, see gh-7535
2564 utils.warn('WARNING: to pass a `session` to `Model.create()` in ' +
2565 'Mongoose, you **must** pass an array as the first argument. See: ' +
2566 'https://mongoosejs.com/docs/api/model.html#Model.create()');
2567 }
2568 }
2569
2570 if (args.length === 0) {
2571 return Array.isArray(doc) ? [] : null;
2572 }
2573 let res = [];
2574 const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;
2575
2576 delete options.aggregateErrors; // dont pass on the option to "$save"
2577
2578 if (options.ordered) {
2579 for (let i = 0; i < args.length; i++) {
2580 try {
2581 const doc = args[i];
2582 const Model = this.discriminators && doc[discriminatorKey] != null ?
2583 this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2584 this;
2585 if (Model == null) {
2586 throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2587 `found for model "${this.modelName}"`);
2588 }
2589 let toSave = doc;
2590 if (!(toSave instanceof Model)) {
2591 toSave = new Model(toSave);
2592 }
2593
2594 await toSave.$save(options);
2595 res.push(toSave);
2596 } catch (err) {
2597 if (!immediateError) {
2598 res.push(err);
2599 } else {
2600 throw err;
2601 }
2602 }
2603 }
2604 return res;
2605 } else if (!immediateError) {
2606 res = await Promise.allSettled(args.map(async doc => {
2607 const Model = this.discriminators && doc[discriminatorKey] != null ?
2608 this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2609 this;
2610 if (Model == null) {
2611 throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2612 `found for model "${this.modelName}"`);
2613 }
2614 let toSave = doc;
2615
2616 if (!(toSave instanceof Model)) {
2617 toSave = new Model(toSave);
2618 }
2619
2620 await toSave.$save(options);
2621
2622 return toSave;
2623 }));
2624 res = res.map(result => result.status === 'fulfilled' ? result.value : result.reason);
2625 } else {
2626 let firstError = null;
2627 res = await Promise.all(args.map(async doc => {
2628 const Model = this.discriminators && doc[discriminatorKey] != null ?
2629 this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2630 this;
2631 if (Model == null) {
2632 throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2633 `found for model "${this.modelName}"`);
2634 }
2635 try {
2636 let toSave = doc;
2637
2638 if (!(toSave instanceof Model)) {
2639 toSave = new Model(toSave);
2640 }
2641
2642 await toSave.$save(options);
2643
2644 return toSave;
2645 } catch (err) {
2646 if (!firstError) {
2647 firstError = err;
2648 }
2649 }
2650 }));
2651 if (firstError) {
2652 throw firstError;
2653 }
2654 }
2655
2656
2657 if (!Array.isArray(doc) && args.length === 1) {
2658 return res[0];
2659 }
2660
2661 return res;
2662};
2663
2664/**
2665 * _Requires a replica set running MongoDB >= 3.6.0._ Watches the
2666 * underlying collection for changes using
2667 * [MongoDB change streams](https://www.mongodb.com/docs/manual/changeStreams/).
2668 *
2669 * This function does **not** trigger any middleware. In particular, it
2670 * does **not** trigger aggregate middleware.
2671 *
2672 * The ChangeStream object is an event emitter that emits the following events:
2673 *
2674 * - 'change': A change occurred, see below example
2675 * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
2676 * - 'end': Emitted if the underlying stream is closed
2677 * - 'close': Emitted if the underlying stream is closed
2678 *
2679 * #### Example:
2680 *
2681 * const doc = await Person.create({ name: 'Ned Stark' });
2682 * const changeStream = Person.watch().on('change', change => console.log(change));
2683 * // Will print from the above `console.log()`:
2684 * // { _id: { _data: ... },
2685 * // operationType: 'delete',
2686 * // ns: { db: 'mydb', coll: 'Person' },
2687 * // documentKey: { _id: 5a51b125c5500f5aa094c7bd } }
2688 * await doc.remove();
2689 *
2690 * @param {Array} [pipeline]
2691 * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#watch)
2692 * @param {Boolean} [options.hydrate=false] if true and `fullDocument: 'updateLookup'` is set, Mongoose will automatically hydrate `fullDocument` into a fully fledged Mongoose document
2693 * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
2694 * @api public
2695 */
2696
2697Model.watch = function(pipeline, options) {
2698 _checkContext(this, 'watch');
2699
2700 const changeStreamThunk = cb => {
2701 pipeline = pipeline || [];
2702 prepareDiscriminatorPipeline(pipeline, this.schema, 'fullDocument');
2703 if (this.$__collection.buffer) {
2704 this.$__collection.addQueue(() => {
2705 if (this.closed) {
2706 return;
2707 }
2708 const driverChangeStream = this.$__collection.watch(pipeline, options);
2709 cb(null, driverChangeStream);
2710 });
2711 } else {
2712 const driverChangeStream = this.$__collection.watch(pipeline, options);
2713 cb(null, driverChangeStream);
2714 }
2715 };
2716
2717 options = options || {};
2718 options.model = this;
2719
2720 return new ChangeStream(changeStreamThunk, pipeline, options);
2721};
2722
2723/**
2724 * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
2725 * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
2726 * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
2727 *
2728 * Calling `MyModel.startSession()` is equivalent to calling `MyModel.db.startSession()`.
2729 *
2730 * This function does not trigger any middleware.
2731 *
2732 * #### Example:
2733 *
2734 * const session = await Person.startSession();
2735 * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
2736 * await doc.remove();
2737 * // `doc` will always be null, even if reading from a replica set
2738 * // secondary. Without causal consistency, it is possible to
2739 * // get a doc back from the below query if the query reads from a
2740 * // secondary that is experiencing replication lag.
2741 * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
2742 *
2743 * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
2744 * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
2745 * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
2746 * @api public
2747 */
2748
2749Model.startSession = function() {
2750 _checkContext(this, 'startSession');
2751
2752 return this.db.startSession.apply(this.db, arguments);
2753};
2754
2755/**
2756 * Shortcut for validating an array of documents and inserting them into
2757 * MongoDB if they're all valid. This function is faster than `.create()`
2758 * because it only sends one operation to the server, rather than one for each
2759 * document.
2760 *
2761 * Mongoose always validates each document **before** sending `insertMany`
2762 * to MongoDB. So if one document has a validation error, no documents will
2763 * be saved, unless you set
2764 * [the `ordered` option to false](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/#error-handling).
2765 *
2766 * This function does **not** trigger save middleware.
2767 *
2768 * This function triggers the following middleware.
2769 *
2770 * - `insertMany()`
2771 *
2772 * #### Example:
2773 *
2774 * const docs = await Movies.insertMany([
2775 * { name: 'Star Wars' },
2776 * { name: 'The Empire Strikes Back' }
2777 * ]);
2778 * docs[0].name; // 'Star Wars'
2779 *
2780 * // Return raw result from MongoDB
2781 * const result = await Movies.insertMany([
2782 * { name: 'Star Wars' },
2783 * { name: 'The Empire Strikes Back' }
2784 * ], { rawResult: true });
2785 *
2786 * @param {Array|Object|*} doc(s)
2787 * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
2788 * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
2789 * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
2790 * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast, validate, or apply defaults to any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](https://mongoosejs.com/docs/api/model.html#Model.castObject()) and [`applyDefaults()`](https://mongoosejs.com/docs/api/model.html#Model.applyDefaults()).
2791 * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
2792 * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
2793 * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
2794 * @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise
2795 * @api public
2796 */
2797
2798Model.insertMany = async function insertMany(arr, options) {
2799 _checkContext(this, 'insertMany');
2800 if (typeof options === 'function' ||
2801 typeof arguments[2] === 'function') {
2802 throw new MongooseError('Model.insertMany() no longer accepts a callback');
2803 }
2804
2805 return new Promise((resolve, reject) => {
2806 this.$__insertMany(arr, options, (err, res) => {
2807 if (err != null) {
2808 return reject(err);
2809 }
2810 resolve(res);
2811 });
2812 });
2813};
2814
2815/**
2816 * ignore
2817 *
2818 * @param {Array} arr
2819 * @param {Object} options
2820 * @param {Function} callback
2821 * @api private
2822 * @memberOf Model
2823 * @method $__insertMany
2824 * @static
2825 */
2826
2827Model.$__insertMany = function(arr, options, callback) {
2828 const _this = this;
2829 if (typeof options === 'function') {
2830 callback = options;
2831 options = null;
2832 }
2833
2834 callback = callback || utils.noop;
2835 options = options || {};
2836 const limit = options.limit || 1000;
2837 const rawResult = !!options.rawResult;
2838 const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
2839 const throwOnValidationError = typeof options.throwOnValidationError === 'boolean' ? options.throwOnValidationError : false;
2840 const lean = !!options.lean;
2841
2842 const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
2843 if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
2844 options = { ...options, session: asyncLocalStorage.session };
2845 }
2846
2847 if (!Array.isArray(arr)) {
2848 arr = [arr];
2849 }
2850
2851 const validationErrors = [];
2852 const validationErrorsToOriginalOrder = new Map();
2853 const results = ordered ? null : new Array(arr.length);
2854 const toExecute = arr.map((doc, index) =>
2855 callback => {
2856 // If option `lean` is set to true bypass validation and hydration
2857 if (lean) {
2858 // we have to execute callback at the nextTick to be compatible
2859 // with parallelLimit, as `results` variable has TDZ issue if we
2860 // execute the callback synchronously
2861 return immediate(() => callback(null, doc));
2862 }
2863 let createdNewDoc = false;
2864 if (!(doc instanceof _this)) {
2865 if (doc != null && typeof doc !== 'object') {
2866 return callback(new ObjectParameterError(doc, 'arr.' + index, 'insertMany'));
2867 }
2868 try {
2869 doc = new _this(doc);
2870 createdNewDoc = true;
2871 } catch (err) {
2872 return callback(err);
2873 }
2874 }
2875
2876 if (options.session != null) {
2877 doc.$session(options.session);
2878 }
2879 // If option `lean` is set to true bypass validation
2880 if (lean) {
2881 // we have to execute callback at the nextTick to be compatible
2882 // with parallelLimit, as `results` variable has TDZ issue if we
2883 // execute the callback synchronously
2884 return immediate(() => callback(null, doc));
2885 }
2886 doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null).then(
2887 () => { callback(null, doc); },
2888 error => {
2889 if (ordered === false) {
2890 validationErrors.push(error);
2891 validationErrorsToOriginalOrder.set(error, index);
2892 results[index] = error;
2893 return callback(null, null);
2894 }
2895 callback(error);
2896 }
2897 );
2898 });
2899
2900 parallelLimit(toExecute, limit, function(error, docs) {
2901 if (error) {
2902 callback(error, null);
2903 return;
2904 }
2905
2906 const originalDocIndex = new Map();
2907 const validDocIndexToOriginalIndex = new Map();
2908 for (let i = 0; i < docs.length; ++i) {
2909 originalDocIndex.set(docs[i], i);
2910 }
2911
2912 // We filter all failed pre-validations by removing nulls
2913 const docAttributes = docs.filter(function(doc) {
2914 return doc != null;
2915 });
2916 for (let i = 0; i < docAttributes.length; ++i) {
2917 validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
2918 }
2919
2920 // Make sure validation errors are in the same order as the
2921 // original documents, so if both doc1 and doc2 both fail validation,
2922 // `Model.insertMany([doc1, doc2])` will always have doc1's validation
2923 // error before doc2's. Re: gh-12791.
2924 if (validationErrors.length > 0) {
2925 validationErrors.sort((err1, err2) => {
2926 return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
2927 });
2928 }
2929
2930 // Quickly escape while there aren't any valid docAttributes
2931 if (docAttributes.length === 0) {
2932 if (throwOnValidationError) {
2933 return callback(new MongooseBulkWriteError(
2934 validationErrors,
2935 results,
2936 null,
2937 'insertMany'
2938 ));
2939 }
2940 if (rawResult) {
2941 const res = {
2942 acknowledged: true,
2943 insertedCount: 0,
2944 insertedIds: {},
2945 mongoose: {
2946 validationErrors: validationErrors
2947 }
2948 };
2949 return callback(null, res);
2950 }
2951 callback(null, []);
2952 return;
2953 }
2954 const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
2955 if (doc.$__schema.options.versionKey) {
2956 doc[doc.$__schema.options.versionKey] = 0;
2957 }
2958 const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
2959 if (shouldSetTimestamps) {
2960 doc.initializeTimestamps();
2961 }
2962 if (doc.$__hasOnlyPrimitiveValues()) {
2963 return doc.$__toObjectShallow();
2964 }
2965 return doc.toObject(internalToObjectOptions);
2966 });
2967
2968 _this.$__collection.insertMany(docObjects, options).then(
2969 res => {
2970 if (!lean) {
2971 for (const attribute of docAttributes) {
2972 attribute.$__reset();
2973 _setIsNew(attribute, false);
2974 }
2975 }
2976
2977 if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
2978 for (let i = 0; i < results.length; ++i) {
2979 if (results[i] === void 0) {
2980 results[i] = docs[i];
2981 }
2982 }
2983 return callback(new MongooseBulkWriteError(
2984 validationErrors,
2985 results,
2986 res,
2987 'insertMany'
2988 ));
2989 }
2990
2991 if (rawResult) {
2992 if (ordered === false) {
2993 for (let i = 0; i < results.length; ++i) {
2994 if (results[i] === void 0) {
2995 results[i] = docs[i];
2996 }
2997 }
2998
2999 // Decorate with mongoose validation errors in case of unordered,
3000 // because then still do `insertMany()`
3001 res.mongoose = {
3002 validationErrors: validationErrors,
3003 results: results
3004 };
3005 }
3006 return callback(null, res);
3007 }
3008
3009 if (options.populate != null) {
3010 return _this.populate(docAttributes, options.populate).then(
3011 docs => { callback(null, docs); },
3012 err => {
3013 if (err != null) {
3014 err.insertedDocs = docAttributes;
3015 }
3016 throw err;
3017 }
3018 );
3019 }
3020
3021 callback(null, docAttributes);
3022 },
3023 error => {
3024 // `writeErrors` is a property reported by the MongoDB driver,
3025 // just not if there's only 1 error.
3026 if (error.writeErrors == null &&
3027 (error.result && error.result.result && error.result.result.writeErrors) != null) {
3028 error.writeErrors = error.result.result.writeErrors;
3029 }
3030
3031 // `insertedDocs` is a Mongoose-specific property
3032 const hasWriteErrors = error && error.writeErrors;
3033 const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));
3034
3035 if (error.writeErrors != null) {
3036 for (let i = 0; i < error.writeErrors.length; ++i) {
3037 const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3038 error.writeErrors[i] = {
3039 ...error.writeErrors[i],
3040 index: originalIndex
3041 };
3042 if (!ordered) {
3043 results[originalIndex] = error.writeErrors[i];
3044 }
3045 }
3046 }
3047
3048 if (!ordered) {
3049 for (let i = 0; i < results.length; ++i) {
3050 if (results[i] === void 0) {
3051 results[i] = docs[i];
3052 }
3053 }
3054
3055 error.results = results;
3056 }
3057
3058 let firstErroredIndex = -1;
3059 error.insertedDocs = docAttributes.
3060 filter((doc, i) => {
3061 const isErrored = !hasWriteErrors || erroredIndexes.has(i);
3062
3063 if (ordered) {
3064 if (firstErroredIndex > -1) {
3065 return i < firstErroredIndex;
3066 }
3067
3068 if (isErrored) {
3069 firstErroredIndex = i;
3070 }
3071 }
3072
3073 return !isErrored;
3074 }).
3075 map(function setIsNewForInsertedDoc(doc) {
3076 if (lean) {
3077 return doc;
3078 }
3079 doc.$__reset();
3080 _setIsNew(doc, false);
3081 return doc;
3082 });
3083
3084 if (rawResult && ordered === false) {
3085 error.mongoose = {
3086 validationErrors: validationErrors,
3087 results: results
3088 };
3089 }
3090
3091 callback(error, null);
3092 }
3093 );
3094 });
3095};
3096
3097/*!
3098 * ignore
3099 */
3100
3101function _setIsNew(doc, val) {
3102 doc.$isNew = val;
3103 doc.$emit('isNew', val);
3104 doc.constructor.emit('isNew', val);
3105
3106 const subdocs = doc.$getAllSubdocs();
3107 for (const subdoc of subdocs) {
3108 subdoc.$isNew = val;
3109 subdoc.$emit('isNew', val);
3110 }
3111}
3112
3113/**
3114 * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`,
3115 * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one
3116 * command. This is faster than sending multiple independent operations (e.g.
3117 * if you use `create()`) because with `bulkWrite()` there is only one round
3118 * trip to MongoDB.
3119 *
3120 * Mongoose will perform casting on all operations you provide.
3121 * The only exception is [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines.
3122 *
3123 * This function does **not** trigger any middleware, neither `save()`, nor `update()`.
3124 * If you need to trigger
3125 * `save()` middleware for every document use [`create()`](https://mongoosejs.com/docs/api/model.html#Model.create()) instead.
3126 *
3127 * #### Example:
3128 *
3129 * Character.bulkWrite([
3130 * {
3131 * insertOne: {
3132 * document: {
3133 * name: 'Eddard Stark',
3134 * title: 'Warden of the North'
3135 * }
3136 * }
3137 * },
3138 * {
3139 * updateOne: {
3140 * filter: { name: 'Eddard Stark' },
3141 * // If you were using the MongoDB driver directly, you'd need to do
3142 * // `update: { $set: { title: ... } }` but mongoose adds $set for
3143 * // you.
3144 * update: { title: 'Hand of the King' }
3145 * }
3146 * },
3147 * {
3148 * deleteOne: {
3149 * filter: { name: 'Eddard Stark' }
3150 * }
3151 * }
3152 * ]).then(res => {
3153 * // Prints "1 1 1"
3154 * console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
3155 * });
3156 *
3157 * // Mongoose does **not** cast update pipelines, so no casting for the `update` option below.
3158 * // Mongoose does still cast `filter`
3159 * await Character.bulkWrite([{
3160 * updateOne: {
3161 * filter: { name: 'Annika Hansen' },
3162 * update: [{ $set: { name: 7 } }] // Array means update pipeline, so Mongoose skips casting
3163 * }
3164 * }]);
3165 *
3166 * The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
3167 *
3168 * - `insertOne`
3169 * - `updateOne`
3170 * - `updateMany`
3171 * - `deleteOne`
3172 * - `deleteMany`
3173 * - `replaceOne`
3174 *
3175 * @param {Array} ops
3176 * @param {Object} [ops.insertOne.document] The document to insert
3177 * @param {Object} [ops.insertOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
3178 * @param {Object} [ops.updateOne.filter] Update the first document that matches this filter
3179 * @param {Object} [ops.updateOne.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
3180 * @param {Boolean} [ops.updateOne.upsert=false] If true, insert a doc if none match
3181 * @param {Boolean} [ops.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
3182 * @param {Object} [ops.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
3183 * @param {Array} [ops.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
3184 * @param {Object} [ops.updateMany.filter] Update all the documents that match this filter
3185 * @param {Object} [ops.updateMany.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
3186 * @param {Boolean} [ops.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
3187 * @param {Boolean} [ops.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
3188 * @param {Object} [ops.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
3189 * @param {Array} [ops.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
3190 * @param {Object} [ops.deleteOne.filter] Delete the first document that matches this filter
3191 * @param {Object} [ops.deleteMany.filter] Delete all documents that match this filter
3192 * @param {Object} [ops.replaceOne.filter] Replace the first document that matches this filter
3193 * @param {Object} [ops.replaceOne.replacement] The replacement document
3194 * @param {Boolean} [ops.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
3195 * @param {Object} [ops.replaceOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
3196 * @param {Object} [options]
3197 * @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored.
3198 * @param {Boolean} [options.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to any operations. Can be overridden at the operation-level.
3199 * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
3200 * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
3201 * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
3202 * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
3203 * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default.
3204 * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
3205 * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
3206 * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
3207 * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
3208 * @api public
3209 */
3210
3211Model.bulkWrite = async function bulkWrite(ops, options) {
3212 _checkContext(this, 'bulkWrite');
3213
3214 if (typeof options === 'function' ||
3215 typeof arguments[2] === 'function') {
3216 throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
3217 }
3218 options = options || {};
3219
3220 const shouldSkip = await new Promise((resolve, reject) => {
3221 this.hooks.execPre('bulkWrite', this, [ops, options], (err) => {
3222 if (err != null) {
3223 if (err instanceof Kareem.skipWrappedFunction) {
3224 return resolve(err);
3225 }
3226 return reject(err);
3227 }
3228 resolve();
3229 });
3230 });
3231
3232 if (shouldSkip) {
3233 return shouldSkip.args[0];
3234 }
3235
3236 const ordered = options.ordered == null ? true : options.ordered;
3237
3238 if (ops.length === 0) {
3239 return getDefaultBulkwriteResult();
3240 }
3241
3242 const validations = ops.map(op => castBulkWrite(this, op, options));
3243 const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
3244 if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
3245 options = { ...options, session: asyncLocalStorage.session };
3246 }
3247
3248 let res = null;
3249 if (ordered) {
3250 await new Promise((resolve, reject) => {
3251 each(validations, (fn, cb) => fn(cb), error => {
3252 if (error) {
3253 return reject(error);
3254 }
3255
3256 resolve();
3257 });
3258 });
3259
3260 try {
3261 res = await this.$__collection.bulkWrite(ops, options);
3262 } catch (error) {
3263 await new Promise((resolve, reject) => {
3264 const _opts = { error: error };
3265 this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
3266 if (err != null) {
3267 return reject(err);
3268 }
3269 resolve();
3270 });
3271 });
3272 }
3273 } else {
3274 let remaining = validations.length;
3275 let validOps = [];
3276 let validationErrors = [];
3277 const results = [];
3278 await new Promise((resolve) => {
3279 for (let i = 0; i < validations.length; ++i) {
3280 validations[i]((err) => {
3281 if (err == null) {
3282 validOps.push(i);
3283 } else {
3284 validationErrors.push({ index: i, error: err });
3285 results[i] = err;
3286 }
3287 if (--remaining <= 0) {
3288 resolve();
3289 }
3290 });
3291 }
3292 });
3293
3294 validationErrors = validationErrors.
3295 sort((v1, v2) => v1.index - v2.index).
3296 map(v => v.error);
3297
3298 const validOpIndexes = validOps;
3299 validOps = validOps.sort().map(index => ops[index]);
3300
3301 if (validOps.length === 0) {
3302 if (options.throwOnValidationError && validationErrors.length) {
3303 throw new MongooseBulkWriteError(
3304 validationErrors,
3305 results,
3306 res,
3307 'bulkWrite'
3308 );
3309 }
3310 return getDefaultBulkwriteResult();
3311 }
3312
3313 let error;
3314 [res, error] = await this.$__collection.bulkWrite(validOps, options).
3315 then(res => ([res, null])).
3316 catch(err => ([null, err]));
3317
3318 if (error) {
3319 if (validationErrors.length > 0) {
3320 error.mongoose = error.mongoose || {};
3321 error.mongoose.validationErrors = validationErrors;
3322 }
3323
3324 await new Promise((resolve, reject) => {
3325 const _opts = { error: error };
3326 this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
3327 if (err != null) {
3328 return reject(err);
3329 }
3330 resolve();
3331 });
3332 });
3333 }
3334
3335 for (let i = 0; i < validOpIndexes.length; ++i) {
3336 results[validOpIndexes[i]] = null;
3337 }
3338 if (validationErrors.length > 0) {
3339 if (options.throwOnValidationError) {
3340 throw new MongooseBulkWriteError(
3341 validationErrors,
3342 results,
3343 res,
3344 'bulkWrite'
3345 );
3346 } else {
3347 res.mongoose = res.mongoose || {};
3348 res.mongoose.validationErrors = validationErrors;
3349 res.mongoose.results = results;
3350 }
3351 }
3352 }
3353
3354 await new Promise((resolve, reject) => {
3355 this.hooks.execPost('bulkWrite', this, [res], (err) => {
3356 if (err != null) {
3357 return reject(err);
3358 }
3359 resolve();
3360 });
3361 });
3362
3363 return res;
3364};
3365
3366/**
3367 * takes an array of documents, gets the changes and inserts/updates documents in the database
3368 * according to whether or not the document is new, or whether it has changes or not.
3369 *
3370 * `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+)
3371 *
3372 * @param {Array<Document>} documents
3373 * @param {Object} [options] options passed to the underlying `bulkWrite()`
3374 * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
3375 * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
3376 * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
3377 * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
3378 * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
3379 *
3380 */
3381Model.bulkSave = async function bulkSave(documents, options) {
3382 options = options || {};
3383
3384 if (options.timestamps != null) {
3385 for (const document of documents) {
3386 document.$__.saveOptions = document.$__.saveOptions || {};
3387 document.$__.saveOptions.timestamps = options.timestamps;
3388 }
3389 } else {
3390 for (const document of documents) {
3391 if (document.$__.timestamps != null) {
3392 document.$__.saveOptions = document.$__.saveOptions || {};
3393 document.$__.saveOptions.timestamps = document.$__.timestamps;
3394 }
3395 }
3396 }
3397
3398 await Promise.all(documents.map(buildPreSavePromise));
3399
3400 const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true, timestamps: options.timestamps });
3401
3402 const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, options).then(
3403 (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
3404 (err) => ({ bulkWriteResult: null, bulkWriteError: err })
3405 );
3406
3407 await Promise.all(
3408 documents.map(async(document) => {
3409 const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
3410 const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3411 return writeErrorDocumentId.toString() === document._id.toString();
3412 });
3413
3414 if (documentError == null) {
3415 await handleSuccessfulWrite(document);
3416 }
3417 })
3418 );
3419
3420 if (bulkWriteError && bulkWriteError.writeErrors && bulkWriteError.writeErrors.length) {
3421 throw bulkWriteError;
3422 }
3423
3424 return bulkWriteResult;
3425};
3426
3427function buildPreSavePromise(document) {
3428 return new Promise((resolve, reject) => {
3429 document.schema.s.hooks.execPre('save', document, (err) => {
3430 if (err) {
3431 reject(err);
3432 return;
3433 }
3434 resolve();
3435 });
3436 });
3437}
3438
3439function handleSuccessfulWrite(document) {
3440 return new Promise((resolve, reject) => {
3441 if (document.$isNew) {
3442 _setIsNew(document, false);
3443 }
3444
3445 document.$__reset();
3446 document.schema.s.hooks.execPost('save', document, [document], {}, (err) => {
3447 if (err) {
3448 reject(err);
3449 return;
3450 }
3451 resolve();
3452 });
3453
3454 });
3455}
3456
3457/**
3458 * Apply defaults to the given document or POJO.
3459 *
3460 * @param {Object|Document} obj object or document to apply defaults on
3461 * @returns {Object|Document}
3462 * @api public
3463 */
3464
3465Model.applyDefaults = function applyDefaults(doc) {
3466 if (doc.$__ != null) {
3467 applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
3468
3469 for (const subdoc of doc.$getAllSubdocs()) {
3470 applyDefaults(subdoc, subdoc.$__.fields, subdoc.$__.exclude);
3471 }
3472
3473 return doc;
3474 }
3475
3476 applyDefaultsToPOJO(doc, this.schema);
3477
3478 return doc;
3479};
3480
3481/**
3482 * Cast the given POJO to the model's schema
3483 *
3484 * #### Example:
3485 *
3486 * const Test = mongoose.model('Test', Schema({ num: Number }));
3487 *
3488 * const obj = Test.castObject({ num: '42' });
3489 * obj.num; // 42 as a number
3490 *
3491 * Test.castObject({ num: 'not a number' }); // Throws a ValidationError
3492 *
3493 * @param {Object} obj object or document to cast
3494 * @param {Object} options options passed to castObject
3495 * @param {Boolean} options.ignoreCastErrors If set to `true` will not throw a ValidationError and only return values that were successfully cast.
3496 * @returns {Object} POJO casted to the model's schema
3497 * @throws {ValidationError} if casting failed for at least one path
3498 * @api public
3499 */
3500
3501Model.castObject = function castObject(obj, options) {
3502 options = options || {};
3503 const ret = {};
3504
3505 const schema = this.schema;
3506 const paths = Object.keys(schema.paths);
3507
3508 for (const path of paths) {
3509 const schemaType = schema.path(path);
3510 if (!schemaType || !schemaType.$isMongooseArray) {
3511 continue;
3512 }
3513
3514 const val = get(obj, path);
3515 pushNestedArrayPaths(paths, val, path);
3516 }
3517
3518 let error = null;
3519
3520 for (const path of paths) {
3521 const schemaType = schema.path(path);
3522 if (schemaType == null) {
3523 continue;
3524 }
3525
3526 let val = get(obj, path, void 0);
3527
3528 if (val == null) {
3529 continue;
3530 }
3531
3532 const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
3533 let cur = ret;
3534 for (let i = 0; i < pieces.length - 1; ++i) {
3535 if (cur[pieces[i]] == null) {
3536 cur[pieces[i]] = isNaN(pieces[i + 1]) ? {} : [];
3537 }
3538 cur = cur[pieces[i]];
3539 }
3540
3541 if (schemaType.$isMongooseDocumentArray) {
3542 continue;
3543 }
3544 if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
3545 try {
3546 val = Model.castObject.call(schemaType.caster, val);
3547 } catch (err) {
3548 if (!options.ignoreCastErrors) {
3549 error = error || new ValidationError();
3550 error.addError(path, err);
3551 }
3552 continue;
3553 }
3554
3555 cur[pieces[pieces.length - 1]] = val;
3556 continue;
3557 }
3558
3559 try {
3560 val = schemaType.cast(val);
3561 cur[pieces[pieces.length - 1]] = val;
3562 } catch (err) {
3563 if (!options.ignoreCastErrors) {
3564 error = error || new ValidationError();
3565 error.addError(path, err);
3566 }
3567
3568 continue;
3569 }
3570 }
3571
3572 if (error != null) {
3573 throw error;
3574 }
3575
3576 return ret;
3577};
3578
3579/**
3580 * Build bulk write operations for `bulkSave()`.
3581 *
3582 * @param {Array<Document>} documents The array of documents to build write operations of
3583 * @param {Object} options
3584 * @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
3585 * @param {Boolean} options.timestamps defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
3586 * @return {Array<Promise>} Returns a array of all Promises the function executes to be awaited.
3587 * @api private
3588 */
3589
3590Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
3591 if (!Array.isArray(documents)) {
3592 throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
3593 }
3594
3595 setDefaultOptions();
3596 const discriminatorKey = this.schema.options.discriminatorKey;
3597
3598 const writeOperations = documents.reduce((accumulator, document, i) => {
3599 if (!options.skipValidation) {
3600 if (!(document instanceof Document)) {
3601 throw new Error(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
3602 }
3603 const validationError = document.validateSync();
3604 if (validationError) {
3605 throw validationError;
3606 }
3607 }
3608
3609 const isANewDocument = document.isNew;
3610 if (isANewDocument) {
3611 const writeOperation = { insertOne: { document } };
3612 utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
3613 accumulator.push(writeOperation);
3614
3615 return accumulator;
3616 }
3617
3618 const delta = document.$__delta();
3619 const isDocumentWithChanges = delta != null && !utils.isEmptyObject(delta[0]);
3620
3621 if (isDocumentWithChanges) {
3622 const where = document.$__where(delta[0]);
3623 const changes = delta[1];
3624
3625 _applyCustomWhere(document, where);
3626
3627 // If shard key is set, add shard keys to _filter_ condition to right shard is targeted
3628 const shardKey = this.schema.options.shardKey;
3629 if (shardKey) {
3630 const paths = Object.keys(shardKey);
3631 const len = paths.length;
3632
3633 for (let i = 0; i < len; ++i) {
3634 where[paths[i]] = document[paths[i]];
3635 }
3636 }
3637
3638 // Set the discriminator key, so bulk write casting knows which
3639 // schema to use re: gh-13907
3640 if (document[discriminatorKey] != null && !(discriminatorKey in where)) {
3641 where[discriminatorKey] = document[discriminatorKey];
3642 }
3643
3644 document.$__version(where, delta);
3645 const writeOperation = { updateOne: { filter: where, update: changes } };
3646 utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
3647 accumulator.push(writeOperation);
3648
3649 return accumulator;
3650 }
3651
3652 return accumulator;
3653 }, []);
3654
3655 return writeOperations;
3656
3657
3658 function setDefaultOptions() {
3659 options = options || {};
3660 if (options.skipValidation == null) {
3661 options.skipValidation = false;
3662 }
3663 }
3664};
3665
3666
3667/**
3668 * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
3669 * The document returned has no paths marked as modified initially.
3670 *
3671 * #### Example:
3672 *
3673 * // hydrate previous data into a Mongoose document
3674 * const mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
3675 *
3676 * @param {Object} obj
3677 * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document
3678 * @param {Object} [options] optional options
3679 * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
3680 * @param {Boolean} [options.hydratedPopulatedDocs=false] if true, populates the docs if passing pre-populated data
3681 * @return {Document} document instance
3682 * @api public
3683 */
3684
3685Model.hydrate = function(obj, projection, options) {
3686 _checkContext(this, 'hydrate');
3687
3688 if (projection != null) {
3689 if (obj != null && obj.$__ != null) {
3690 obj = obj.toObject(internalToObjectOptions);
3691 }
3692 obj = applyProjection(obj, projection);
3693 }
3694 const document = require('./queryHelpers').createModel(this, obj, projection);
3695 document.$init(obj, options);
3696 return document;
3697};
3698
3699/**
3700 * Same as `updateOne()`, except MongoDB will update _all_ documents that match
3701 * `filter` (as opposed to just the first one) regardless of the value of
3702 * the `multi` option.
3703 *
3704 * **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
3705 * and `post('updateMany')` instead.
3706 *
3707 * #### Example:
3708 *
3709 * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
3710 * res.matchedCount; // Number of documents matched
3711 * res.modifiedCount; // Number of documents modified
3712 * res.acknowledged; // Boolean indicating everything went smoothly.
3713 * res.upsertedId; // null or an id containing a document that had to be upserted.
3714 * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3715 *
3716 * This function triggers the following middleware.
3717 *
3718 * - `updateMany()`
3719 *
3720 * @param {Object} filter
3721 * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
3722 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3723 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3724 * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3725 * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3726 * @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. Does nothing if schema-level timestamps are not set.
3727 * @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.
3728 * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
3729 * @return {Query}
3730 * @see Query docs https://mongoosejs.com/docs/queries.html
3731 * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
3732 * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
3733 * @api public
3734 */
3735
3736Model.updateMany = function updateMany(conditions, doc, options) {
3737 _checkContext(this, 'updateMany');
3738
3739 return _update(this, 'updateMany', conditions, doc, options);
3740};
3741
3742/**
3743 * Update _only_ the first document that matches `filter`.
3744 *
3745 * - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
3746 *
3747 * #### Example:
3748 *
3749 * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
3750 * res.matchedCount; // Number of documents matched
3751 * res.modifiedCount; // Number of documents modified
3752 * res.acknowledged; // Boolean indicating everything went smoothly.
3753 * res.upsertedId; // null or an id containing a document that had to be upserted.
3754 * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3755 *
3756 * This function triggers the following middleware.
3757 *
3758 * - `updateOne()`
3759 *
3760 * @param {Object} filter
3761 * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
3762 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3763 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3764 * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3765 * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3766 * @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.
3767 * @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.
3768 * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
3769 * @return {Query}
3770 * @see Query docs https://mongoosejs.com/docs/queries.html
3771 * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
3772 * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
3773 * @api public
3774 */
3775
3776Model.updateOne = function updateOne(conditions, doc, options) {
3777 _checkContext(this, 'updateOne');
3778
3779 return _update(this, 'updateOne', conditions, doc, options);
3780};
3781
3782/**
3783 * Replace the existing document with the given document (no atomic operators like `$set`).
3784 *
3785 * #### Example:
3786 *
3787 * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
3788 * res.matchedCount; // Number of documents matched
3789 * res.modifiedCount; // Number of documents modified
3790 * res.acknowledged; // Boolean indicating everything went smoothly.
3791 * res.upsertedId; // null or an id containing a document that had to be upserted.
3792 * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3793 *
3794 * This function triggers the following middleware.
3795 *
3796 * - `replaceOne()`
3797 *
3798 * @param {Object} filter
3799 * @param {Object} doc
3800 * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3801 * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3802 * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3803 * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3804 * @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. Does nothing if schema-level timestamps are not set.
3805 * @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.
3806 * @return {Query}
3807 * @see Query docs https://mongoosejs.com/docs/queries.html
3808 * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
3809 * @return {Query}
3810 * @api public
3811 */
3812
3813Model.replaceOne = function replaceOne(conditions, doc, options) {
3814 _checkContext(this, 'replaceOne');
3815
3816 const versionKey = this && this.schema && this.schema.options && this.schema.options.versionKey || null;
3817 if (versionKey && !doc[versionKey]) {
3818 doc[versionKey] = 0;
3819 }
3820
3821 return _update(this, 'replaceOne', conditions, doc, options);
3822};
3823
3824/**
3825 * Common code for `updateOne()`, `updateMany()`, `replaceOne()`, and `update()`
3826 * because they need to do the same thing
3827 * @api private
3828 */
3829
3830function _update(model, op, conditions, doc, options) {
3831 const mq = new model.Query({}, {}, model, model.collection);
3832
3833 // gh-2406
3834 // make local deep copy of conditions
3835 if (conditions instanceof Document) {
3836 conditions = conditions.toObject();
3837 } else {
3838 conditions = clone(conditions);
3839 }
3840 options = typeof options === 'function' ? options : clone(options);
3841
3842 const versionKey = model &&
3843 model.schema &&
3844 model.schema.options &&
3845 model.schema.options.versionKey || null;
3846 decorateUpdateWithVersionKey(doc, options, versionKey);
3847
3848 return mq[op](conditions, doc, options);
3849}
3850
3851/**
3852 * Performs [aggregations](https://www.mongodb.com/docs/manual/aggregation/) on the models collection.
3853 *
3854 * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned.
3855 *
3856 * This function triggers the following middleware.
3857 *
3858 * - `aggregate()`
3859 *
3860 * #### Example:
3861 *
3862 * // Find the max balance of all accounts
3863 * const res = await Users.aggregate([
3864 * { $group: { _id: null, maxBalance: { $max: '$balance' }}},
3865 * { $project: { _id: 0, maxBalance: 1 }}
3866 * ]);
3867 *
3868 * console.log(res); // [ { maxBalance: 98000 } ]
3869 *
3870 * // Or use the aggregation pipeline builder.
3871 * const res = await Users.aggregate().
3872 * group({ _id: null, maxBalance: { $max: '$balance' } }).
3873 * project('-id maxBalance').
3874 * exec();
3875 * console.log(res); // [ { maxBalance: 98 } ]
3876 *
3877 * #### Note:
3878 *
3879 * - Mongoose does **not** cast aggregation pipelines to the model's schema because `$project` and `$group` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. You can use the [mongoose-cast-aggregation plugin](https://github.com/AbdelrahmanHafez/mongoose-cast-aggregation) to enable minimal casting for aggregation pipelines.
3880 * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
3881 *
3882 * #### More About Aggregations:
3883 *
3884 * - [Mongoose `Aggregate`](https://mongoosejs.com/docs/api/aggregate.html)
3885 * - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate)
3886 * - [MongoDB Aggregation docs](https://www.mongodb.com/docs/manual/applications/aggregation/)
3887 *
3888 * @see Aggregate https://mongoosejs.com/docs/api/aggregate.html#Aggregate()
3889 * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
3890 * @param {Array} [pipeline] aggregation pipeline as an array of objects
3891 * @param {Object} [options] aggregation options
3892 * @return {Aggregate}
3893 * @api public
3894 */
3895
3896Model.aggregate = function aggregate(pipeline, options) {
3897 _checkContext(this, 'aggregate');
3898
3899 if (typeof options === 'function' || typeof arguments[2] === 'function') {
3900 throw new MongooseError('Model.aggregate() no longer accepts a callback');
3901 }
3902
3903 const aggregate = new Aggregate(pipeline || []);
3904 aggregate.model(this);
3905 if (options != null) {
3906 aggregate.option(options);
3907 }
3908
3909 if (typeof callback === 'undefined') {
3910 return aggregate;
3911 }
3912
3913 return aggregate;
3914};
3915
3916/**
3917 * Casts and validates the given object against this model's schema, passing the
3918 * given `context` to custom validators.
3919 *
3920 * #### Example:
3921 *
3922 * const Model = mongoose.model('Test', Schema({
3923 * name: { type: String, required: true },
3924 * age: { type: Number, required: true }
3925 * });
3926 *
3927 * try {
3928 * await Model.validate({ name: null }, ['name'])
3929 * } catch (err) {
3930 * err instanceof mongoose.Error.ValidationError; // true
3931 * Object.keys(err.errors); // ['name']
3932 * }
3933 *
3934 * @param {Object} obj
3935 * @param {Object|Array|String} pathsOrOptions
3936 * @param {Object} [context]
3937 * @return {Promise<Object>} casted and validated copy of `obj` if validation succeeded
3938 * @api public
3939 */
3940
3941Model.validate = async function validate(obj, pathsOrOptions, context) {
3942 if ((arguments.length < 3) || (arguments.length === 3 && typeof arguments[2] === 'function')) {
3943 // For convenience, if we're validating a document or an object, make `context` default to
3944 // the model so users don't have to always pass `context`, re: gh-10132, gh-10346
3945 context = obj;
3946 }
3947 if (typeof context === 'function' || typeof arguments[3] === 'function') {
3948 throw new MongooseError('Model.validate() no longer accepts a callback');
3949 }
3950
3951 let schema = this.schema;
3952 const discriminatorKey = schema.options.discriminatorKey;
3953 if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
3954 schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
3955 }
3956 let paths = Object.keys(schema.paths);
3957
3958 if (pathsOrOptions != null) {
3959 const _pathsToValidate = typeof pathsOrOptions === 'string' ? new Set(pathsOrOptions.split(' ')) : Array.isArray(pathsOrOptions) ? new Set(pathsOrOptions) : new Set(paths);
3960 paths = paths.filter(p => {
3961 if (pathsOrOptions.pathsToSkip) {
3962 if (Array.isArray(pathsOrOptions.pathsToSkip)) {
3963 if (pathsOrOptions.pathsToSkip.find(x => x == p)) {
3964 return false;
3965 }
3966 } else if (typeof pathsOrOptions.pathsToSkip == 'string') {
3967 if (pathsOrOptions.pathsToSkip.includes(p)) {
3968 return false;
3969 }
3970 }
3971 }
3972 const pieces = p.split('.');
3973 let cur = pieces[0];
3974
3975 for (const piece of pieces) {
3976 if (_pathsToValidate.has(cur)) {
3977 return true;
3978 }
3979 cur += '.' + piece;
3980 }
3981
3982 return _pathsToValidate.has(p);
3983 });
3984 }
3985
3986 for (const path of paths) {
3987 const schemaType = schema.path(path);
3988 if (!schemaType || !schemaType.$isMongooseArray || schemaType.$isMongooseDocumentArray) {
3989 continue;
3990 }
3991
3992 const val = get(obj, path);
3993 pushNestedArrayPaths(paths, val, path);
3994 }
3995
3996 let error = null;
3997 paths = new Set(paths);
3998
3999 try {
4000 obj = this.castObject(obj);
4001 } catch (err) {
4002 error = err;
4003 for (const key of Object.keys(error.errors || {})) {
4004 paths.delete(key);
4005 }
4006 }
4007
4008 let remaining = paths.size;
4009
4010 return new Promise((resolve, reject) => {
4011 for (const path of paths) {
4012 const schemaType = schema.path(path);
4013 if (schemaType == null) {
4014 _checkDone();
4015 continue;
4016 }
4017
4018 const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
4019 let cur = obj;
4020 for (let i = 0; i < pieces.length - 1; ++i) {
4021 cur = cur[pieces[i]];
4022 }
4023
4024 const val = get(obj, path, void 0);
4025
4026 schemaType.doValidate(val, err => {
4027 if (err) {
4028 error = error || new ValidationError();
4029 error.addError(path, err);
4030 }
4031 _checkDone();
4032 }, context, { path: path });
4033 }
4034
4035 function _checkDone() {
4036 if (--remaining <= 0) {
4037 if (error) {
4038 reject(error);
4039 } else {
4040 resolve(obj);
4041 }
4042 }
4043 }
4044 });
4045};
4046
4047/**
4048 * Populates document references.
4049 *
4050 * Changed in Mongoose 6: the model you call `populate()` on should be the
4051 * "local field" model, **not** the "foreign field" model.
4052 *
4053 * #### Available top-level options:
4054 *
4055 * - path: space delimited path(s) to populate
4056 * - select: optional fields to select
4057 * - match: optional query conditions to match
4058 * - model: optional name of the model to use for population
4059 * - options: optional query options like sort, limit, etc
4060 * - justOne: optional boolean, if true Mongoose will always set `path` to a document, or `null` if no document was found. If false, Mongoose will always set `path` to an array, which will be empty if no documents are found. Inferred from schema by default.
4061 * - strictPopulate: optional boolean, set to `false` to allow populating paths that aren't in the schema.
4062 *
4063 * #### Example:
4064 *
4065 * const Dog = mongoose.model('Dog', new Schema({ name: String, breed: String }));
4066 * const Person = mongoose.model('Person', new Schema({
4067 * name: String,
4068 * pet: { type: mongoose.ObjectId, ref: 'Dog' }
4069 * }));
4070 *
4071 * const pets = await Pet.create([
4072 * { name: 'Daisy', breed: 'Beagle' },
4073 * { name: 'Einstein', breed: 'Catalan Sheepdog' }
4074 * ]);
4075 *
4076 * // populate many plain objects
4077 * const users = [
4078 * { name: 'John Wick', dog: pets[0]._id },
4079 * { name: 'Doc Brown', dog: pets[1]._id }
4080 * ];
4081 * await User.populate(users, { path: 'dog', select: 'name' });
4082 * users[0].dog.name; // 'Daisy'
4083 * users[0].dog.breed; // undefined because of `select`
4084 *
4085 * @param {Document|Array} docs Either a single document or array of documents to populate.
4086 * @param {Object|String} options Either the paths to populate or an object specifying all parameters
4087 * @param {string} [options.path=null] The path to populate.
4088 * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate).
4089 * @param {boolean} [options.retainNullValues=false] By default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
4090 * @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options).
4091 * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
4092 * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
4093 * @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
4094 * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
4095 * @param {Boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
4096 * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
4097 * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
4098 * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
4099 * @return {Promise}
4100 * @api public
4101 */
4102
4103Model.populate = async function populate(docs, paths) {
4104 _checkContext(this, 'populate');
4105 if (typeof paths === 'function' || typeof arguments[2] === 'function') {
4106 throw new MongooseError('Model.populate() no longer accepts a callback');
4107 }
4108 const _this = this;
4109 // normalized paths
4110 paths = utils.populate(paths);
4111 // data that should persist across subPopulate calls
4112 const cache = {};
4113
4114 return new Promise((resolve, reject) => {
4115 _populate(_this, docs, paths, cache, (err, res) => {
4116 if (err) {
4117 return reject(err);
4118 }
4119 resolve(res);
4120 });
4121 });
4122};
4123
4124/**
4125 * Populate helper
4126 *
4127 * @param {Model} model the model to use
4128 * @param {Document|Array} docs Either a single document or array of documents to populate.
4129 * @param {Object} paths
4130 * @param {never} cache Unused
4131 * @param {Function} [callback] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
4132 * @return {Function}
4133 * @api private
4134 */
4135
4136function _populate(model, docs, paths, cache, callback) {
4137 let pending = paths.length;
4138 if (paths.length === 0) {
4139 return callback(null, docs);
4140 }
4141 // each path has its own query options and must be executed separately
4142 for (const path of paths) {
4143 populate(model, docs, path, next);
4144 }
4145
4146 function next(err) {
4147 if (err) {
4148 return callback(err, null);
4149 }
4150 if (--pending) {
4151 return;
4152 }
4153 callback(null, docs);
4154 }
4155}
4156
4157/*!
4158 * Populates `docs`
4159 */
4160const excludeIdReg = /\s?-_id\s?/;
4161const excludeIdRegGlobal = /\s?-_id\s?/g;
4162
4163function populate(model, docs, options, callback) {
4164 const populateOptions = options;
4165 if (options.strictPopulate == null) {
4166 if (options._localModel != null && options._localModel.schema._userProvidedOptions.strictPopulate != null) {
4167 populateOptions.strictPopulate = options._localModel.schema._userProvidedOptions.strictPopulate;
4168 } else if (options._localModel != null && model.base.options.strictPopulate != null) {
4169 populateOptions.strictPopulate = model.base.options.strictPopulate;
4170 } else if (model.base.options.strictPopulate != null) {
4171 populateOptions.strictPopulate = model.base.options.strictPopulate;
4172 }
4173 }
4174
4175 // normalize single / multiple docs passed
4176 if (!Array.isArray(docs)) {
4177 docs = [docs];
4178 }
4179 if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) {
4180 return callback();
4181 }
4182
4183 const modelsMap = getModelsMapForPopulate(model, docs, populateOptions);
4184
4185 if (modelsMap instanceof MongooseError) {
4186 return immediate(function() {
4187 callback(modelsMap);
4188 });
4189 }
4190 const len = modelsMap.length;
4191 let vals = [];
4192
4193 function flatten(item) {
4194 // no need to include undefined values in our query
4195 return undefined !== item;
4196 }
4197
4198 let _remaining = len;
4199 let hasOne = false;
4200 const params = [];
4201 for (let i = 0; i < len; ++i) {
4202 const mod = modelsMap[i];
4203 let select = mod.options.select;
4204 let ids = utils.array.flatten(mod.ids, flatten);
4205 ids = utils.array.unique(ids);
4206
4207 const assignmentOpts = {};
4208 assignmentOpts.sort = mod &&
4209 mod.options &&
4210 mod.options.options &&
4211 mod.options.options.sort || void 0;
4212 assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
4213
4214 // Lean transform may delete `_id`, which would cause assignment
4215 // to fail. So delay running lean transform until _after_
4216 // `_assign()`
4217 if (mod.options &&
4218 mod.options.options &&
4219 mod.options.options.lean &&
4220 mod.options.options.lean.transform) {
4221 mod.options.options._leanTransform = mod.options.options.lean.transform;
4222 mod.options.options.lean = true;
4223 }
4224
4225 if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
4226 // Ensure that we set to 0 or empty array even
4227 // if we don't actually execute a query to make sure there's a value
4228 // and we know this path was populated for future sets. See gh-7731, gh-8230
4229 --_remaining;
4230 _assign(model, [], mod, assignmentOpts);
4231 continue;
4232 }
4233
4234 hasOne = true;
4235 if (typeof populateOptions.foreignField === 'string') {
4236 mod.foreignField.clear();
4237 mod.foreignField.add(populateOptions.foreignField);
4238 }
4239 const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
4240 if (assignmentOpts.excludeId) {
4241 // override the exclusion from the query so we can use the _id
4242 // for document matching during assignment. we'll delete the
4243 // _id back off before returning the result.
4244 if (typeof select === 'string') {
4245 select = select.replace(excludeIdRegGlobal, ' ');
4246 } else if (Array.isArray(select)) {
4247 select = select.filter(field => field !== '-_id');
4248 } else {
4249 // preserve original select conditions by copying
4250 select = { ...select };
4251 delete select._id;
4252 }
4253 }
4254
4255 if (mod.options.options && mod.options.options.limit != null) {
4256 assignmentOpts.originalLimit = mod.options.options.limit;
4257 } else if (mod.options.limit != null) {
4258 assignmentOpts.originalLimit = mod.options.limit;
4259 }
4260 params.push([mod, match, select, assignmentOpts, _next]);
4261 }
4262 if (!hasOne) {
4263 // If models but no docs, skip further deep populate.
4264 if (modelsMap.length !== 0) {
4265 return callback();
4266 }
4267 // If no models to populate but we have a nested populate,
4268 // keep trying, re: gh-8946
4269 if (populateOptions.populate != null) {
4270 const opts = utils.populate(populateOptions.populate).map(pop => Object.assign({}, pop, {
4271 path: populateOptions.path + '.' + pop.path
4272 }));
4273 model.populate(docs, opts).then(res => { callback(null, res); }, err => { callback(err); });
4274 return;
4275 }
4276 return callback();
4277 }
4278
4279 for (const arr of params) {
4280 _execPopulateQuery.apply(null, arr);
4281 }
4282 function _next(err, valsFromDb) {
4283 if (err != null) {
4284 return callback(err, null);
4285 }
4286 vals = vals.concat(valsFromDb);
4287 if (--_remaining === 0) {
4288 _done();
4289 }
4290 }
4291
4292 function _done() {
4293 for (const arr of params) {
4294 const mod = arr[0];
4295 const assignmentOpts = arr[3];
4296 for (const val of vals) {
4297 mod.options._childDocs.push(val);
4298 }
4299 try {
4300 _assign(model, vals, mod, assignmentOpts);
4301 } catch (err) {
4302 return callback(err);
4303 }
4304 }
4305
4306 for (const arr of params) {
4307 removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals);
4308 }
4309 for (const arr of params) {
4310 const mod = arr[0];
4311 if (mod.options && mod.options.options && mod.options.options._leanTransform) {
4312 for (const doc of vals) {
4313 mod.options.options._leanTransform(doc);
4314 }
4315 }
4316 }
4317 callback();
4318 }
4319}
4320
4321/*!
4322 * ignore
4323 */
4324
4325function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
4326 let subPopulate = clone(mod.options.populate);
4327 const queryOptions = Object.assign({
4328 skip: mod.options.skip,
4329 limit: mod.options.limit,
4330 perDocumentLimit: mod.options.perDocumentLimit
4331 }, mod.options.options);
4332
4333 if (mod.count) {
4334 delete queryOptions.skip;
4335 }
4336
4337 if (queryOptions.perDocumentLimit != null) {
4338 queryOptions.limit = queryOptions.perDocumentLimit;
4339 delete queryOptions.perDocumentLimit;
4340 } else if (queryOptions.limit != null) {
4341 queryOptions.limit = queryOptions.limit * mod.ids.length;
4342 }
4343
4344 const query = mod.model.find(match, select, queryOptions);
4345 // If we're doing virtual populate and projection is inclusive and foreign
4346 // field is not selected, automatically select it because mongoose needs it.
4347 // If projection is exclusive and client explicitly unselected the foreign
4348 // field, that's the client's fault.
4349 for (const foreignField of mod.foreignField) {
4350 if (foreignField !== '_id' &&
4351 query.selectedInclusively() &&
4352 !isPathSelectedInclusive(query._fields, foreignField)) {
4353 query.select(foreignField);
4354 }
4355 }
4356
4357 // If using count, still need the `foreignField` so we can match counts
4358 // to documents, otherwise we would need a separate `count()` for every doc.
4359 if (mod.count) {
4360 for (const foreignField of mod.foreignField) {
4361 query.select(foreignField);
4362 }
4363 }
4364
4365 // If we need to sub-populate, call populate recursively
4366 if (subPopulate) {
4367 // If subpopulating on a discriminator, skip check for non-existent
4368 // paths. Because the discriminator may not have the path defined.
4369 if (mod.model.baseModelName != null) {
4370 if (Array.isArray(subPopulate)) {
4371 subPopulate.forEach(pop => { pop.strictPopulate = false; });
4372 } else if (typeof subPopulate === 'string') {
4373 subPopulate = { path: subPopulate, strictPopulate: false };
4374 } else {
4375 subPopulate.strictPopulate = false;
4376 }
4377 }
4378 const basePath = mod.options._fullPath || mod.options.path;
4379
4380 if (Array.isArray(subPopulate)) {
4381 for (const pop of subPopulate) {
4382 pop._fullPath = basePath + '.' + pop.path;
4383 }
4384 } else if (typeof subPopulate === 'object') {
4385 subPopulate._fullPath = basePath + '.' + subPopulate.path;
4386 }
4387
4388 query.populate(subPopulate);
4389 }
4390
4391 query.exec().then(
4392 docs => {
4393 for (const val of docs) {
4394 leanPopulateMap.set(val, mod.model);
4395 }
4396 callback(null, docs);
4397 },
4398 err => {
4399 callback(err);
4400 }
4401 );
4402}
4403
4404/*!
4405 * ignore
4406 */
4407
4408function _assign(model, vals, mod, assignmentOpts) {
4409 const options = mod.options;
4410 const isVirtual = mod.isVirtual;
4411 const justOne = mod.justOne;
4412 let _val;
4413 const lean = options &&
4414 options.options &&
4415 options.options.lean || false;
4416 const len = vals.length;
4417 const rawOrder = {};
4418 const rawDocs = {};
4419 let key;
4420 let val;
4421
4422 // Clone because `assignRawDocsToIdStructure` will mutate the array
4423 const allIds = clone(mod.allIds);
4424 // optimization:
4425 // record the document positions as returned by
4426 // the query result.
4427 for (let i = 0; i < len; i++) {
4428 val = vals[i];
4429 if (val == null) {
4430 continue;
4431 }
4432 for (const foreignField of mod.foreignField) {
4433 _val = utils.getValue(foreignField, val);
4434 if (Array.isArray(_val)) {
4435 _val = utils.array.unique(utils.array.flatten(_val));
4436
4437 for (let __val of _val) {
4438 if (__val instanceof Document) {
4439 __val = __val._id;
4440 }
4441 key = String(__val);
4442 if (rawDocs[key]) {
4443 if (Array.isArray(rawDocs[key])) {
4444 rawDocs[key].push(val);
4445 rawOrder[key].push(i);
4446 } else {
4447 rawDocs[key] = [rawDocs[key], val];
4448 rawOrder[key] = [rawOrder[key], i];
4449 }
4450 } else {
4451 if (isVirtual && !justOne) {
4452 rawDocs[key] = [val];
4453 rawOrder[key] = [i];
4454 } else {
4455 rawDocs[key] = val;
4456 rawOrder[key] = i;
4457 }
4458 }
4459 }
4460 } else {
4461 if (_val instanceof Document) {
4462 _val = _val._doc._id;
4463 }
4464 key = String(_val);
4465 if (rawDocs[key]) {
4466 if (Array.isArray(rawDocs[key])) {
4467 rawDocs[key].push(val);
4468 rawOrder[key].push(i);
4469 } else if (isVirtual ||
4470 rawDocs[key].constructor !== val.constructor ||
4471 (rawDocs[key] instanceof Document ? String(rawDocs[key]._doc._id) : String(rawDocs[key]._id)) !== (val instanceof Document ? String(val._doc._id) : String(val._id))) {
4472 // May need to store multiple docs with the same id if there's multiple models
4473 // if we have discriminators or a ref function. But avoid converting to an array
4474 // if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906
4475 rawDocs[key] = [rawDocs[key], val];
4476 rawOrder[key] = [rawOrder[key], i];
4477 }
4478 } else {
4479 rawDocs[key] = val;
4480 rawOrder[key] = i;
4481 }
4482 }
4483 // flag each as result of population
4484 if (!lean) {
4485 val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
4486 }
4487 }
4488 }
4489
4490 assignVals({
4491 originalModel: model,
4492 // If virtual, make sure to not mutate original field
4493 rawIds: mod.isVirtual ? allIds : mod.allIds,
4494 allIds: allIds,
4495 unpopulatedValues: mod.unpopulatedValues,
4496 foreignField: mod.foreignField,
4497 rawDocs: rawDocs,
4498 rawOrder: rawOrder,
4499 docs: mod.docs,
4500 path: options.path,
4501 options: assignmentOpts,
4502 justOne: mod.justOne,
4503 isVirtual: mod.isVirtual,
4504 allOptions: mod,
4505 populatedModel: mod.model,
4506 lean: lean,
4507 virtual: mod.virtual,
4508 count: mod.count,
4509 match: mod.match
4510 });
4511}
4512
4513/**
4514 * Compiler utility.
4515 *
4516 * @param {String|Function} name model name or class extending Model
4517 * @param {Schema} schema
4518 * @param {String} collectionName
4519 * @param {Connection} connection
4520 * @param {Mongoose} base mongoose instance
4521 * @api private
4522 */
4523
4524Model.compile = function compile(name, schema, collectionName, connection, base) {
4525 const versioningEnabled = schema.options.versionKey !== false;
4526
4527 if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
4528 // add versioning to top level documents only
4529 const o = {};
4530 o[schema.options.versionKey] = Number;
4531 schema.add(o);
4532 }
4533 let model;
4534 if (typeof name === 'function' && name.prototype instanceof Model) {
4535 model = name;
4536 name = model.name;
4537 schema.loadClass(model, false);
4538 model.prototype.$isMongooseModelPrototype = true;
4539 } else {
4540 // generate new class
4541 model = function model(doc, fields, skipId) {
4542 model.hooks.execPreSync('createModel', doc);
4543 if (!(this instanceof model)) {
4544 return new model(doc, fields, skipId);
4545 }
4546 const discriminatorKey = model.schema.options.discriminatorKey;
4547
4548 if (model.discriminators == null || doc == null || doc[discriminatorKey] == null) {
4549 Model.call(this, doc, fields, skipId);
4550 return;
4551 }
4552
4553 // If discriminator key is set, use the discriminator instead (gh-7586)
4554 const Discriminator = model.discriminators[doc[discriminatorKey]] ||
4555 getDiscriminatorByValue(model.discriminators, doc[discriminatorKey]);
4556 if (Discriminator != null) {
4557 return new Discriminator(doc, fields, skipId);
4558 }
4559
4560 // Otherwise, just use the top-level model
4561 Model.call(this, doc, fields, skipId);
4562 };
4563 }
4564
4565 model.hooks = schema.s.hooks.clone();
4566 model.base = base;
4567 model.modelName = name;
4568
4569 if (!(model.prototype instanceof Model)) {
4570 Object.setPrototypeOf(model, Model);
4571 Object.setPrototypeOf(model.prototype, Model.prototype);
4572 }
4573 model.model = function model(name) {
4574 return this.db.model(name);
4575 };
4576
4577 model.db = connection;
4578 model.prototype.db = connection;
4579 model.prototype[modelDbSymbol] = connection;
4580 model.discriminators = model.prototype.discriminators = undefined;
4581 model[modelSymbol] = true;
4582 model.events = new EventEmitter();
4583
4584 schema._preCompile();
4585
4586 const _userProvidedOptions = schema._userProvidedOptions || {};
4587
4588 const collectionOptions = {
4589 schemaUserProvidedOptions: _userProvidedOptions,
4590 capped: schema.options.capped,
4591 Promise: model.base.Promise,
4592 modelName: name
4593 };
4594 if (schema.options.autoCreate !== void 0) {
4595 collectionOptions.autoCreate = schema.options.autoCreate;
4596 }
4597
4598 const collection = connection.collection(
4599 collectionName,
4600 collectionOptions
4601 );
4602
4603 model.prototype.collection = collection;
4604 model.prototype.$collection = collection;
4605 model.prototype[modelCollectionSymbol] = collection;
4606
4607 model.prototype.$__setSchema(schema);
4608
4609 // apply methods and statics
4610 applyMethods(model, schema);
4611 applyStatics(model, schema);
4612 applyHooks(model, schema);
4613 applyStaticHooks(model, schema.s.hooks, schema.statics);
4614
4615 model.schema = model.prototype.$__schema;
4616 model.collection = collection;
4617 model.$__collection = collection;
4618
4619 // Create custom query constructor
4620 model.Query = function() {
4621 Query.apply(this, arguments);
4622 };
4623 Object.setPrototypeOf(model.Query.prototype, Query.prototype);
4624 model.Query.base = Query.base;
4625 model.Query.prototype.constructor = Query;
4626 model._applyQueryMiddleware();
4627 applyQueryMethods(model, schema.query);
4628
4629 return model;
4630};
4631
4632/**
4633 * Update this model to use the new connection, including updating all internal
4634 * references and creating a new `Collection` instance using the new connection.
4635 * Not for external use, only used by `setDriver()` to ensure that you can still
4636 * call `setDriver()` after creating a model using `mongoose.model()`.
4637 *
4638 * @param {Connection} newConnection the new connection to use
4639 * @api private
4640 */
4641
4642Model.$__updateConnection = function $__updateConnection(newConnection) {
4643 this.db = newConnection;
4644 this.prototype.db = newConnection;
4645 this.prototype[modelDbSymbol] = newConnection;
4646
4647 const collection = newConnection.collection(
4648 this.collection.collectionName,
4649 this.collection.opts
4650 );
4651
4652 this.prototype.collection = collection;
4653 this.prototype.$collection = collection;
4654 this.prototype[modelCollectionSymbol] = collection;
4655
4656 this.collection = collection;
4657 this.$__collection = collection;
4658};
4659
4660/**
4661 * Register custom query methods for this model
4662 *
4663 * @param {Model} model
4664 * @param {Schema} schema
4665 * @api private
4666 */
4667
4668function applyQueryMethods(model, methods) {
4669 for (const i in methods) {
4670 model.Query.prototype[i] = methods[i];
4671 }
4672}
4673
4674/**
4675 * Subclass this model with `conn`, `schema`, and `collection` settings.
4676 *
4677 * @param {Connection} conn
4678 * @param {Schema} [schema]
4679 * @param {String} [collection]
4680 * @return {Model}
4681 * @api private
4682 * @memberOf Model
4683 * @static
4684 * @method __subclass
4685 */
4686
4687Model.__subclass = function subclass(conn, schema, collection) {
4688 // subclass model using this connection and collection name
4689 const _this = this;
4690
4691 const Model = function Model(doc, fields, skipId) {
4692 if (!(this instanceof Model)) {
4693 return new Model(doc, fields, skipId);
4694 }
4695 _this.call(this, doc, fields, skipId);
4696 };
4697
4698 Object.setPrototypeOf(Model, _this);
4699 Object.setPrototypeOf(Model.prototype, _this.prototype);
4700 Model.db = conn;
4701 Model.prototype.db = conn;
4702 Model.prototype[modelDbSymbol] = conn;
4703
4704 _this[subclassedSymbol] = _this[subclassedSymbol] || [];
4705 _this[subclassedSymbol].push(Model);
4706 if (_this.discriminators != null) {
4707 Model.discriminators = {};
4708 for (const key of Object.keys(_this.discriminators)) {
4709 Model.discriminators[key] = _this.discriminators[key].
4710 __subclass(_this.db, _this.discriminators[key].schema, collection);
4711 }
4712 }
4713
4714 const s = schema && typeof schema !== 'string'
4715 ? schema
4716 : _this.prototype.$__schema;
4717
4718 const options = s.options || {};
4719 const _userProvidedOptions = s._userProvidedOptions || {};
4720
4721 if (!collection) {
4722 collection = _this.prototype.$__schema.get('collection') ||
4723 utils.toCollectionName(_this.modelName, this.base.pluralize());
4724 }
4725
4726 const collectionOptions = {
4727 schemaUserProvidedOptions: _userProvidedOptions,
4728 capped: s && options.capped
4729 };
4730
4731 Model.prototype.collection = conn.collection(collection, collectionOptions);
4732 Model.prototype.$collection = Model.prototype.collection;
4733 Model.prototype[modelCollectionSymbol] = Model.prototype.collection;
4734 Model.collection = Model.prototype.collection;
4735 Model.$__collection = Model.collection;
4736 // Errors handled internally, so ignore
4737 Model.init().catch(() => {});
4738 return Model;
4739};
4740
4741/**
4742 * Apply changes made to this model's schema after this model was compiled.
4743 * By default, adding virtuals and other properties to a schema after the model is compiled does nothing.
4744 * Call this function to apply virtuals and properties that were added later.
4745 *
4746 * #### Example:
4747 *
4748 * const schema = new mongoose.Schema({ field: String });
4749 * const TestModel = mongoose.model('Test', schema);
4750 * TestModel.schema.virtual('myVirtual').get(function() {
4751 * return this.field + ' from myVirtual';
4752 * });
4753 * const doc = new TestModel({ field: 'Hello' });
4754 * doc.myVirtual; // undefined
4755 *
4756 * TestModel.recompileSchema();
4757 * doc.myVirtual; // 'Hello from myVirtual'
4758 *
4759 * @return {undefined}
4760 * @api public
4761 * @memberOf Model
4762 * @static
4763 * @method recompileSchema
4764 */
4765
4766Model.recompileSchema = function recompileSchema() {
4767 this.prototype.$__setSchema(this.schema);
4768
4769 if (this.schema._applyDiscriminators != null) {
4770 for (const disc of this.schema._applyDiscriminators.keys()) {
4771 this.discriminator(disc, this.schema._applyDiscriminators.get(disc));
4772 }
4773 }
4774
4775 delete this.schema._defaultToObjectOptionsMap;
4776
4777 applyEmbeddedDiscriminators(this.schema, new WeakSet(), true);
4778};
4779
4780/**
4781 * Helper for console.log. Given a model named 'MyModel', returns the string
4782 * `'Model { MyModel }'`.
4783 *
4784 * #### Example:
4785 *
4786 * const MyModel = mongoose.model('Test', Schema({ name: String }));
4787 * MyModel.inspect(); // 'Model { Test }'
4788 * console.log(MyModel); // Prints 'Model { Test }'
4789 *
4790 * @api public
4791 */
4792
4793Model.inspect = function() {
4794 return `Model { ${this.modelName} }`;
4795};
4796
4797if (util.inspect.custom) {
4798 // Avoid Node deprecation warning DEP0079
4799 Model[util.inspect.custom] = Model.inspect;
4800}
4801
4802/*!
4803 * Applies query middleware from this model's schema to this model's
4804 * Query constructor.
4805 */
4806
4807Model._applyQueryMiddleware = function _applyQueryMiddleware() {
4808 const Query = this.Query;
4809 const queryMiddleware = this.schema.s.hooks.filter(hook => {
4810 const contexts = _getContexts(hook);
4811 if (hook.name === 'validate') {
4812 return !!contexts.query;
4813 }
4814 if (hook.name === 'deleteOne' || hook.name === 'updateOne') {
4815 return !!contexts.query || Object.keys(contexts).length === 0;
4816 }
4817 if (hook.query != null || hook.document != null) {
4818 return !!hook.query;
4819 }
4820 return true;
4821 });
4822
4823 Query.prototype._queryMiddleware = queryMiddleware;
4824};
4825
4826function _getContexts(hook) {
4827 const ret = {};
4828 if (hook.hasOwnProperty('query')) {
4829 ret.query = hook.query;
4830 }
4831 if (hook.hasOwnProperty('document')) {
4832 ret.document = hook.document;
4833 }
4834 return ret;
4835}
4836
4837/*!
4838 * Module exports.
4839 */
4840
4841module.exports = exports = Model;