UNPKG

102 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies.
5 */
6
7const EventEmitter = require('events').EventEmitter;
8const InternalCache = require('./internal');
9const MongooseError = require('./error/index');
10const MixedSchema = require('./schema/mixed');
11const ObjectExpectedError = require('./error/objectExpected');
12const ObjectParameterError = require('./error/objectParameter');
13const ParallelValidateError = require('./error/parallelValidate');
14const Schema = require('./schema');
15const StrictModeError = require('./error/strict');
16const ValidationError = require('./error/validation');
17const ValidatorError = require('./error/validator');
18const VirtualType = require('./virtualtype');
19const promiseOrCallback = require('./helpers/promiseOrCallback');
20const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
21const compile = require('./helpers/document/compile').compile;
22const defineKey = require('./helpers/document/compile').defineKey;
23const flatten = require('./helpers/common').flatten;
24const get = require('./helpers/get');
25const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
26const idGetter = require('./plugins/idGetter');
27const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
28const isExclusive = require('./helpers/projection/isExclusive');
29const inspect = require('util').inspect;
30const internalToObjectOptions = require('./options').internalToObjectOptions;
31const mpath = require('mpath');
32const utils = require('./utils');
33
34const clone = utils.clone;
35const deepEqual = utils.deepEqual;
36const isMongooseObject = utils.isMongooseObject;
37
38const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
39const documentArrayParent = require('./helpers/symbols').documentArrayParent;
40const documentSchemaSymbol = require('./helpers/symbols').documentSchemaSymbol;
41const getSymbol = require('./helpers/symbols').getSymbol;
42const populateModelSymbol = require('./helpers/symbols').populateModelSymbol;
43
44let DocumentArray;
45let MongooseArray;
46let Embedded;
47
48const specialProperties = utils.specialProperties;
49
50/**
51 * The core Mongoose document constructor. You should not call this directly,
52 * the Mongoose [Model constructor](./api.html#Model) calls this for you.
53 *
54 * @param {Object} obj the values to set
55 * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
56 * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
57 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
58 * @event `init`: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
59 * @event `save`: Emitted when the document is successfully saved
60 * @api private
61 */
62
63function Document(obj, fields, skipId, options) {
64 if (typeof skipId === 'object' && skipId != null) {
65 options = skipId;
66 skipId = options.skipId;
67 }
68 options = options || {};
69
70 // Support `browserDocument.js` syntax
71 if (this.schema == null) {
72 const _schema = utils.isObject(fields) && !fields.instanceOfSchema ?
73 new Schema(fields) :
74 fields;
75 this.$__setSchema(_schema);
76 fields = skipId;
77 skipId = options;
78 options = arguments[4] || {};
79 }
80
81 this.$__ = new InternalCache;
82 this.$__.emitter = new EventEmitter();
83 this.isNew = 'isNew' in options ? options.isNew : true;
84 this.errors = undefined;
85 this.$__.$options = options || {};
86
87 if (obj != null && typeof obj !== 'object') {
88 throw new ObjectParameterError(obj, 'obj', 'Document');
89 }
90
91 const schema = this.schema;
92
93 if (typeof fields === 'boolean') {
94 this.$__.strictMode = fields;
95 fields = undefined;
96 } else {
97 this.$__.strictMode = schema.options.strict;
98 this.$__.selected = fields;
99 }
100
101 const required = schema.requiredPaths(true);
102 for (let i = 0; i < required.length; ++i) {
103 this.$__.activePaths.require(required[i]);
104 }
105
106 this.$__.emitter.setMaxListeners(0);
107
108 let exclude = null;
109
110 // determine if this doc is a result of a query with
111 // excluded fields
112 if (utils.isPOJO(fields)) {
113 exclude = isExclusive(fields);
114 }
115
116 const hasIncludedChildren = exclude === false && fields ?
117 $__hasIncludedChildren(fields) :
118 {};
119
120 if (this._doc == null) {
121 this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
122
123 // By default, defaults get applied **before** setting initial values
124 // Re: gh-6155
125 $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
126 isNew: this.isNew
127 });
128 }
129
130 if (obj) {
131 if (obj instanceof Document) {
132 this.isNew = obj.isNew;
133 }
134 // Skip set hooks
135 if (this.$__original_set) {
136 this.$__original_set(obj, undefined, true);
137 } else {
138 this.$set(obj, undefined, true);
139 }
140 }
141
142 // Function defaults get applied **after** setting initial values so they
143 // see the full doc rather than an empty one, unless they opt out.
144 // Re: gh-3781, gh-6155
145 if (options.willInit) {
146 EventEmitter.prototype.once.call(this, 'init', () => {
147 $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
148 isNew: this.isNew
149 });
150 });
151 } else {
152 $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
153 isNew: this.isNew
154 });
155 }
156
157 this.$__._id = this._id;
158 this.$locals = {};
159 this.$op = null;
160
161 if (!schema.options.strict && obj) {
162 const _this = this;
163 const keys = Object.keys(this._doc);
164
165 keys.forEach(function(key) {
166 if (!(key in schema.tree)) {
167 defineKey(key, null, _this);
168 }
169 });
170 }
171
172 applyQueue(this);
173}
174
175/*!
176 * Document exposes the NodeJS event emitter API, so you can use
177 * `on`, `once`, etc.
178 */
179utils.each(
180 ['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
181 'removeAllListeners', 'addListener'],
182 function(emitterFn) {
183 Document.prototype[emitterFn] = function() {
184 return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments);
185 };
186 });
187
188Document.prototype.constructor = Document;
189
190for (const i in EventEmitter.prototype) {
191 Document[i] = EventEmitter.prototype[i];
192}
193
194/**
195 * The documents schema.
196 *
197 * @api public
198 * @property schema
199 * @memberOf Document
200 * @instance
201 */
202
203Document.prototype.schema;
204
205/**
206 * Empty object that you can use for storing properties on the document. This
207 * is handy for passing data to middleware without conflicting with Mongoose
208 * internals.
209 *
210 * ####Example:
211 *
212 * schema.pre('save', function() {
213 * // Mongoose will set `isNew` to `false` if `save()` succeeds
214 * this.$locals.wasNew = this.isNew;
215 * });
216 *
217 * schema.post('save', function() {
218 * // Prints true if `isNew` was set before `save()`
219 * console.log(this.$locals.wasNew);
220 * });
221 *
222 * @api public
223 * @property $locals
224 * @memberOf Document
225 * @instance
226 */
227
228Object.defineProperty(Document.prototype, '$locals', {
229 configurable: false,
230 enumerable: false,
231 writable: true
232});
233
234/**
235 * Boolean flag specifying if the document is new.
236 *
237 * @api public
238 * @property isNew
239 * @memberOf Document
240 * @instance
241 */
242
243Document.prototype.isNew;
244
245/**
246 * The string version of this documents _id.
247 *
248 * ####Note:
249 *
250 * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time.
251 *
252 * new Schema({ name: String }, { id: false });
253 *
254 * @api public
255 * @see Schema options /docs/guide.html#options
256 * @property id
257 * @memberOf Document
258 * @instance
259 */
260
261Document.prototype.id;
262
263/**
264 * Hash containing current validation errors.
265 *
266 * @api public
267 * @property errors
268 * @memberOf Document
269 * @instance
270 */
271
272Document.prototype.errors;
273
274/**
275 * A string containing the current operation that Mongoose is executing
276 * on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`.
277 *
278 * ####Example:
279 *
280 * const doc = new Model({ name: 'test' });
281 * doc.$op; // null
282 *
283 * const promise = doc.save();
284 * doc.$op; // 'save'
285 *
286 * await promise;
287 * doc.$op; // null
288 *
289 * @api public
290 * @property $op
291 * @memberOf Document
292 * @instance
293 */
294
295Document.prototype.$op;
296
297/*!
298 * ignore
299 */
300
301function $__hasIncludedChildren(fields) {
302 const hasIncludedChildren = {};
303 const keys = Object.keys(fields);
304 for (let j = 0; j < keys.length; ++j) {
305 const parts = keys[j].split('.');
306 const c = [];
307 for (let k = 0; k < parts.length; ++k) {
308 c.push(parts[k]);
309 hasIncludedChildren[c.join('.')] = 1;
310 }
311 }
312
313 return hasIncludedChildren;
314}
315
316/*!
317 * ignore
318 */
319
320function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
321 const paths = Object.keys(doc.schema.paths);
322 const plen = paths.length;
323
324 for (let i = 0; i < plen; ++i) {
325 let def;
326 let curPath = '';
327 const p = paths[i];
328
329 if (p === '_id' && skipId) {
330 continue;
331 }
332
333 const type = doc.schema.paths[p];
334 const path = p.indexOf('.') === -1 ? [p] : p.split('.');
335 const len = path.length;
336 let included = false;
337 let doc_ = doc._doc;
338
339 for (let j = 0; j < len; ++j) {
340 if (doc_ == null) {
341 break;
342 }
343
344 const piece = path[j];
345 curPath += (!curPath.length ? '' : '.') + piece;
346
347 if (exclude === true) {
348 if (curPath in fields) {
349 break;
350 }
351 } else if (exclude === false && fields && !included) {
352 if (curPath in fields) {
353 included = true;
354 } else if (!hasIncludedChildren[curPath]) {
355 break;
356 }
357 }
358
359 if (j === len - 1) {
360 if (doc_[piece] !== void 0) {
361 break;
362 }
363
364 if (typeof type.defaultValue === 'function') {
365 if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
366 break;
367 }
368 if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
369 break;
370 }
371 } else if (!isBeforeSetters) {
372 // Non-function defaults should always run **before** setters
373 continue;
374 }
375
376 if (pathsToSkip && pathsToSkip[curPath]) {
377 break;
378 }
379
380 if (fields && exclude !== null) {
381 if (exclude === true) {
382 // apply defaults to all non-excluded fields
383 if (p in fields) {
384 continue;
385 }
386
387 def = type.getDefault(doc, false);
388 if (typeof def !== 'undefined') {
389 doc_[piece] = def;
390 doc.$__.activePaths.default(p);
391 }
392 } else if (included) {
393 // selected field
394 def = type.getDefault(doc, false);
395 if (typeof def !== 'undefined') {
396 doc_[piece] = def;
397 doc.$__.activePaths.default(p);
398 }
399 }
400 } else {
401 def = type.getDefault(doc, false);
402 if (typeof def !== 'undefined') {
403 doc_[piece] = def;
404 doc.$__.activePaths.default(p);
405 }
406 }
407 } else {
408 doc_ = doc_[piece];
409 }
410 }
411 }
412}
413
414/**
415 * Builds the default doc structure
416 *
417 * @param {Object} obj
418 * @param {Object} [fields]
419 * @param {Boolean} [skipId]
420 * @api private
421 * @method $__buildDoc
422 * @memberOf Document
423 * @instance
424 */
425
426Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) {
427 const doc = {};
428
429 const paths = Object.keys(this.schema.paths).
430 // Don't build up any paths that are underneath a map, we don't know
431 // what the keys will be
432 filter(p => !p.includes('$*'));
433 const plen = paths.length;
434 let ii = 0;
435
436 for (; ii < plen; ++ii) {
437 const p = paths[ii];
438
439 if (p === '_id') {
440 if (skipId) {
441 continue;
442 }
443 if (obj && '_id' in obj) {
444 continue;
445 }
446 }
447
448 const path = p.split('.');
449 const len = path.length;
450 const last = len - 1;
451 let curPath = '';
452 let doc_ = doc;
453 let included = false;
454
455 for (let i = 0; i < len; ++i) {
456 const piece = path[i];
457
458 curPath += (!curPath.length ? '' : '.') + piece;
459
460 // support excluding intermediary levels
461 if (exclude === true) {
462 if (curPath in fields) {
463 break;
464 }
465 } else if (exclude === false && fields && !included) {
466 if (curPath in fields) {
467 included = true;
468 } else if (!hasIncludedChildren[curPath]) {
469 break;
470 }
471 }
472
473 if (i < last) {
474 doc_ = doc_[piece] || (doc_[piece] = {});
475 }
476 }
477 }
478
479 this._doc = doc;
480};
481
482/*!
483 * Converts to POJO when you use the document for querying
484 */
485
486Document.prototype.toBSON = function() {
487 return this.toObject(internalToObjectOptions);
488};
489
490/**
491 * Initializes the document without setters or marking anything modified.
492 *
493 * Called internally after a document is returned from mongodb. Normally,
494 * you do **not** need to call this function on your own.
495 *
496 * This function triggers `init` [middleware](/docs/middleware.html).
497 * Note that `init` hooks are [synchronous](/docs/middleware.html#synchronous).
498 *
499 * @param {Object} doc document returned by mongo
500 * @api public
501 * @memberOf Document
502 * @instance
503 */
504
505Document.prototype.init = function(doc, opts, fn) {
506 if (typeof opts === 'function') {
507 fn = opts;
508 opts = null;
509 }
510
511 this.$__init(doc, opts);
512
513 if (fn) {
514 fn(null, this);
515 }
516
517 return this;
518};
519
520/*!
521 * ignore
522 */
523
524Document.prototype.$__init = function(doc, opts) {
525 this.isNew = false;
526 this.$init = true;
527 opts = opts || {};
528
529 // handle docs with populated paths
530 // If doc._id is not null or undefined
531 if (doc._id != null && opts.populated && opts.populated.length) {
532 const id = String(doc._id);
533 for (let i = 0; i < opts.populated.length; ++i) {
534 const item = opts.populated[i];
535 if (item.isVirtual) {
536 this.populated(item.path, utils.getValue(item.path, doc), item);
537 } else {
538 this.populated(item.path, item._docs[id], item);
539 }
540 }
541 }
542
543 init(this, doc, this._doc, opts);
544
545 markArraySubdocsPopulated(this, opts.populated);
546
547 this.emit('init', this);
548 this.constructor.emit('init', this);
549
550 this.$__._id = this._id;
551
552 return this;
553};
554
555/*!
556 * If populating a path within a document array, make sure each
557 * subdoc within the array knows its subpaths are populated.
558 *
559 * ####Example:
560 * const doc = await Article.findOne().populate('comments.author');
561 * doc.comments[0].populated('author'); // Should be set
562 */
563
564function markArraySubdocsPopulated(doc, populated) {
565 if (doc._id == null || populated == null || populated.length === 0) {
566 return;
567 }
568
569 const id = String(doc._id);
570 for (const item of populated) {
571 if (item.isVirtual) {
572 continue;
573 }
574 const path = item.path;
575 const pieces = path.split('.');
576 for (let i = 0; i < pieces.length - 1; ++i) {
577 const subpath = pieces.slice(0, i + 1).join('.');
578 const rest = pieces.slice(i + 1).join('.');
579 const val = doc.get(subpath);
580 if (val == null) {
581 continue;
582 }
583 if (val.isMongooseDocumentArray) {
584 for (let j = 0; j < val.length; ++j) {
585 val[j].populated(rest, item._docs[id] == null ? [] : item._docs[id][j], item);
586 }
587 break;
588 }
589 }
590 }
591}
592
593/*!
594 * Init helper.
595 *
596 * @param {Object} self document instance
597 * @param {Object} obj raw mongodb doc
598 * @param {Object} doc object we are initializing
599 * @api private
600 */
601
602function init(self, obj, doc, opts, prefix) {
603 prefix = prefix || '';
604
605 const keys = Object.keys(obj);
606 const len = keys.length;
607 let schema;
608 let path;
609 let i;
610 let index = 0;
611
612 while (index < len) {
613 _init(index++);
614 }
615
616 function _init(index) {
617 i = keys[index];
618 path = prefix + i;
619 schema = self.schema.path(path);
620
621 // Should still work if not a model-level discriminator, but should not be
622 // necessary. This is *only* to catch the case where we queried using the
623 // base model and the discriminated model has a projection
624 if (self.schema.$isRootDiscriminator && !self.isSelected(path)) {
625 return;
626 }
627
628 if (!schema && utils.isPOJO(obj[i])) {
629 // assume nested object
630 if (!doc[i]) {
631 doc[i] = {};
632 }
633 init(self, obj[i], doc[i], opts, path + '.');
634 } else if (!schema) {
635 doc[i] = obj[i];
636 } else {
637 if (obj[i] === null) {
638 doc[i] = null;
639 } else if (obj[i] !== undefined) {
640 const intCache = obj[i].$__ || {};
641 const wasPopulated = intCache.wasPopulated || null;
642
643 if (schema && !wasPopulated) {
644 try {
645 doc[i] = schema.cast(obj[i], self, true);
646 } catch (e) {
647 self.invalidate(e.path, new ValidatorError({
648 path: e.path,
649 message: e.message,
650 type: 'cast',
651 value: e.value
652 }));
653 }
654 } else {
655 doc[i] = obj[i];
656 }
657 }
658 // mark as hydrated
659 if (!self.isModified(path)) {
660 self.$__.activePaths.init(path);
661 }
662 }
663 }
664}
665
666/**
667 * Sends an update command with this document `_id` as the query selector.
668 *
669 * ####Example:
670 *
671 * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
672 *
673 * ####Valid options:
674 *
675 * - same as in [Model.update](#model_Model.update)
676 *
677 * @see Model.update #model_Model.update
678 * @param {Object} doc
679 * @param {Object} options
680 * @param {Function} callback
681 * @return {Query}
682 * @api public
683 * @memberOf Document
684 * @instance
685 */
686
687Document.prototype.update = function update() {
688 const args = utils.args(arguments);
689 args.unshift({_id: this._id});
690 const query = this.constructor.update.apply(this.constructor, args);
691
692 if (this.$session() != null) {
693 if (!('session' in query.options)) {
694 query.options.session = this.$session();
695 }
696 }
697
698 return query;
699};
700
701/**
702 * Sends an updateOne command with this document `_id` as the query selector.
703 *
704 * ####Example:
705 *
706 * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
707 *
708 * ####Valid options:
709 *
710 * - same as in [Model.updateOne](#model_Model.updateOne)
711 *
712 * @see Model.updateOne #model_Model.updateOne
713 * @param {Object} doc
714 * @param {Object} options
715 * @param {Function} callback
716 * @return {Query}
717 * @api public
718 * @memberOf Document
719 * @instance
720 */
721
722Document.prototype.updateOne = function updateOne(doc, options, callback) {
723 const query = this.constructor.updateOne({_id: this._id}, doc, options);
724 query._pre(cb => {
725 this.constructor._middleware.execPre('updateOne', this, [this], cb);
726 });
727 query._post(cb => {
728 this.constructor._middleware.execPost('updateOne', this, [this], {}, cb);
729 });
730
731 if (this.$session() != null) {
732 if (!('session' in query.options)) {
733 query.options.session = this.$session();
734 }
735 }
736
737 if (callback != null) {
738 return query.exec(callback);
739 }
740
741 return query;
742};
743
744/**
745 * Sends a replaceOne command with this document `_id` as the query selector.
746 *
747 * ####Valid options:
748 *
749 * - same as in [Model.replaceOne](#model_Model.replaceOne)
750 *
751 * @see Model.replaceOne #model_Model.replaceOne
752 * @param {Object} doc
753 * @param {Object} options
754 * @param {Function} callback
755 * @return {Query}
756 * @api public
757 * @memberOf Document
758 * @instance
759 */
760
761Document.prototype.replaceOne = function replaceOne() {
762 const args = utils.args(arguments);
763 args.unshift({ _id: this._id });
764 return this.constructor.replaceOne.apply(this.constructor, args);
765};
766
767/**
768 * Getter/setter around the session associated with this document. Used to
769 * automatically set `session` if you `save()` a doc that you got from a
770 * query with an associated session.
771 *
772 * ####Example:
773 *
774 * const session = MyModel.startSession();
775 * const doc = await MyModel.findOne().session(session);
776 * doc.$session() === session; // true
777 * doc.$session(null);
778 * doc.$session() === null; // true
779 *
780 * If this is a top-level document, setting the session propagates to all child
781 * docs.
782 *
783 * @param {ClientSession} [session] overwrite the current session
784 * @return {ClientSession}
785 * @method $session
786 * @api public
787 * @memberOf Document
788 */
789
790Document.prototype.$session = function $session(session) {
791 if (arguments.length === 0) {
792 return this.$__.session;
793 }
794 this.$__.session = session;
795
796 if (!this.ownerDocument) {
797 const subdocs = this.$__getAllSubdocs();
798 for (const child of subdocs) {
799 child.$session(session);
800 }
801 }
802
803 return session;
804};
805
806/**
807 * Overwrite all values in this document with the values of `obj`, except
808 * for immutable properties. Behaves similarly to `set()`, except for it
809 * unsets all properties that aren't in `obj`.
810 *
811 * @param {Object} obj the object to overwrite this document with
812 * @method overwrite
813 * @name overwrite
814 * @memberOf Document
815 * @instance
816 * @api public
817 */
818
819Document.prototype.overwrite = function overwrite(obj) {
820 const keys = Array.from(new Set(Object.keys(this._doc).concat(Object.keys(obj))));
821
822 for (const key of keys) {
823 if (key === '_id') {
824 continue;
825 }
826 // Explicitly skip version key
827 if (this.schema.options.versionKey && key === this.schema.options.versionKey) {
828 continue;
829 }
830 this.$set(key, obj[key]);
831 }
832
833 return this;
834};
835
836/**
837 * Alias for `set()`, used internally to avoid conflicts
838 *
839 * @param {String|Object} path path or object of key/vals to set
840 * @param {Any} val the value to set
841 * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
842 * @param {Object} [options] optionally specify options that modify the behavior of the set
843 * @method $set
844 * @name $set
845 * @memberOf Document
846 * @instance
847 * @api public
848 */
849
850Document.prototype.$set = function $set(path, val, type, options) {
851 if (utils.isPOJO(type)) {
852 options = type;
853 type = undefined;
854 }
855
856 options = options || {};
857 const merge = options.merge;
858 const adhoc = type && type !== true;
859 const constructing = type === true;
860 let adhocs;
861 let keys;
862 let i = 0;
863 let pathtype;
864 let key;
865 let prefix;
866
867 const strict = 'strict' in options
868 ? options.strict
869 : this.$__.strictMode;
870
871 if (adhoc) {
872 adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
873 adhocs[path] = this.schema.interpretAsType(path, type, this.schema.options);
874 }
875
876 if (typeof path !== 'string') {
877 // new Document({ key: val })
878 if (path instanceof Document) {
879 if (path.$__isNested) {
880 path = path.toObject();
881 } else {
882 path = path._doc;
883 }
884 }
885
886 if (path == null) {
887 const _ = path;
888 path = val;
889 val = _;
890 } else {
891 prefix = val ? val + '.' : '';
892
893 keys = Object.keys(path);
894 const len = keys.length;
895
896 // `_skipMinimizeTopLevel` is because we may have deleted the top-level
897 // nested key to ensure key order.
898 const _skipMinimizeTopLevel = get(options, '_skipMinimizeTopLevel', false);
899 if (len === 0 && (!this.schema.options.minimize || _skipMinimizeTopLevel)) {
900 delete options._skipMinimizeTopLevel;
901 if (val) {
902 this.$set(val, {});
903 }
904 return this;
905 }
906
907 while (i < len) {
908 _handleIndex.call(this, i++);
909 }
910
911 return this;
912 }
913 } else {
914 this.$__.$setCalled.add(path);
915 }
916
917 function _handleIndex(i) {
918 key = keys[i];
919 const pathName = prefix + key;
920 pathtype = this.schema.pathType(pathName);
921
922 // On initial set, delete any nested keys if we're going to overwrite
923 // them to ensure we keep the user's key order.
924 if (type === true &&
925 !prefix &&
926 path[key] != null &&
927 pathtype === 'nested' &&
928 this._doc[key] != null &&
929 Object.keys(this._doc[key]).length === 0) {
930 delete this._doc[key];
931 // Make sure we set `{}` back even if we minimize re: gh-8565
932 options = Object.assign({}, options, { _skipMinimizeTopLevel: true });
933 }
934
935 if (typeof path[key] === 'object' &&
936 !utils.isNativeObject(path[key]) &&
937 !utils.isMongooseType(path[key]) &&
938 path[key] != null &&
939 pathtype !== 'virtual' &&
940 pathtype !== 'real' &&
941 !(this.$__path(pathName) instanceof MixedSchema) &&
942 !(this.schema.paths[pathName] &&
943 this.schema.paths[pathName].options &&
944 this.schema.paths[pathName].options.ref)) {
945 this.$__.$setCalled.add(prefix + key);
946 this.$set(path[key], prefix + key, constructing, options);
947 } else if (strict) {
948 // Don't overwrite defaults with undefined keys (gh-3981)
949 if (constructing && path[key] === void 0 &&
950 this.get(key) !== void 0) {
951 return;
952 }
953
954 if (pathtype === 'adhocOrUndefined') {
955 pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true });
956 }
957
958 if (pathtype === 'real' || pathtype === 'virtual') {
959 // Check for setting single embedded schema to document (gh-3535)
960 let p = path[key];
961 if (this.schema.paths[pathName] &&
962 this.schema.paths[pathName].$isSingleNested &&
963 path[key] instanceof Document) {
964 p = p.toObject({ virtuals: false, transform: false });
965 }
966 this.$set(prefix + key, p, constructing, options);
967 } else if (pathtype === 'nested' && path[key] instanceof Document) {
968 this.$set(prefix + key,
969 path[key].toObject({transform: false}), constructing, options);
970 } else if (strict === 'throw') {
971 if (pathtype === 'nested') {
972 throw new ObjectExpectedError(key, path[key]);
973 } else {
974 throw new StrictModeError(key);
975 }
976 }
977 } else if (path[key] !== void 0) {
978 this.$set(prefix + key, path[key], constructing, options);
979 }
980 }
981
982 let pathType = this.schema.pathType(path);
983 if (pathType === 'adhocOrUndefined') {
984 pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true });
985 }
986
987 // Assume this is a Mongoose document that was copied into a POJO using
988 // `Object.assign()` or `{...doc}`
989 if (utils.isPOJO(val) && val.$__ != null && val._doc != null) {
990 val = val._doc;
991 }
992
993 if (pathType === 'nested' && val) {
994 if (typeof val === 'object' && val != null) {
995 if (!merge) {
996 this.$__setValue(path, null);
997 cleanModifiedSubpaths(this, path);
998 } else {
999 return this.$set(val, path, constructing);
1000 }
1001
1002 const keys = Object.keys(val);
1003 this.$__setValue(path, {});
1004 for (const key of keys) {
1005 this.$set(path + '.' + key, val[key], constructing);
1006 }
1007 this.markModified(path);
1008 cleanModifiedSubpaths(this, path, { skipDocArrays: true });
1009 return this;
1010 }
1011 this.invalidate(path, new MongooseError.CastError('Object', val, path));
1012 return this;
1013 }
1014
1015 let schema;
1016 const parts = path.indexOf('.') === -1 ? [path] : path.split('.');
1017
1018 // Might need to change path for top-level alias
1019 if (typeof this.schema.aliases[parts[0]] == 'string') {
1020 parts[0] = this.schema.aliases[parts[0]];
1021 }
1022
1023 if (pathType === 'adhocOrUndefined' && strict) {
1024 // check for roots that are Mixed types
1025 let mixed;
1026
1027 for (i = 0; i < parts.length; ++i) {
1028 const subpath = parts.slice(0, i + 1).join('.');
1029
1030 // If path is underneath a virtual, bypass everything and just set it.
1031 if (i + 1 < parts.length && this.schema.pathType(subpath) === 'virtual') {
1032 mpath.set(path, val, this);
1033 return this;
1034 }
1035
1036 schema = this.schema.path(subpath);
1037 if (schema == null) {
1038 continue;
1039 }
1040
1041 if (schema instanceof MixedSchema) {
1042 // allow changes to sub paths of mixed types
1043 mixed = true;
1044 break;
1045 }
1046 }
1047
1048 if (schema == null) {
1049 // Check for embedded discriminators
1050 schema = getEmbeddedDiscriminatorPath(this, path);
1051 }
1052
1053 if (!mixed && !schema) {
1054 if (strict === 'throw') {
1055 throw new StrictModeError(path);
1056 }
1057 return this;
1058 }
1059 } else if (pathType === 'virtual') {
1060 schema = this.schema.virtualpath(path);
1061 schema.applySetters(val, this);
1062 return this;
1063 } else {
1064 schema = this.$__path(path);
1065 }
1066
1067 // gh-4578, if setting a deeply nested path that doesn't exist yet, create it
1068 let cur = this._doc;
1069 let curPath = '';
1070 for (i = 0; i < parts.length - 1; ++i) {
1071 cur = cur[parts[i]];
1072 curPath += (curPath.length > 0 ? '.' : '') + parts[i];
1073 if (!cur) {
1074 this.$set(curPath, {});
1075 // Hack re: gh-5800. If nested field is not selected, it probably exists
1076 // so `MongoError: cannot use the part (nested of nested.num) to
1077 // traverse the element ({nested: null})` is not likely. If user gets
1078 // that error, its their fault for now. We should reconsider disallowing
1079 // modifying not selected paths for 6.x
1080 if (!this.isSelected(curPath)) {
1081 this.unmarkModified(curPath);
1082 }
1083 cur = this.$__getValue(curPath);
1084 }
1085 }
1086
1087 let pathToMark;
1088
1089 // When using the $set operator the path to the field must already exist.
1090 // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
1091
1092 if (parts.length <= 1) {
1093 pathToMark = path;
1094 } else {
1095 for (i = 0; i < parts.length; ++i) {
1096 const subpath = parts.slice(0, i + 1).join('.');
1097 if (this.get(subpath, null, { getters: false }) === null) {
1098 pathToMark = subpath;
1099 break;
1100 }
1101 }
1102
1103 if (!pathToMark) {
1104 pathToMark = path;
1105 }
1106 }
1107
1108 // if this doc is being constructed we should not trigger getters
1109 const priorVal = (() => {
1110 if (this.$__.$options.priorDoc != null) {
1111 return this.$__.$options.priorDoc.$__getValue(path);
1112 }
1113 if (constructing) {
1114 return void 0;
1115 }
1116 return this.$__getValue(path);
1117 })();
1118
1119 if (!schema) {
1120 this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
1121 return this;
1122 }
1123
1124 if (schema.$isSingleNested && val != null && merge) {
1125 if (val instanceof Document) {
1126 val = val.toObject({ virtuals: false, transform: false });
1127 }
1128 const keys = Object.keys(val);
1129 for (const key of keys) {
1130 this.$set(path + '.' + key, val[key], constructing, options);
1131 }
1132
1133 return this;
1134 }
1135
1136 let shouldSet = true;
1137 try {
1138 // If the user is trying to set a ref path to a document with
1139 // the correct model name, treat it as populated
1140 const refMatches = (() => {
1141 if (schema.options == null) {
1142 return false;
1143 }
1144 if (!(val instanceof Document)) {
1145 return false;
1146 }
1147 const model = val.constructor;
1148
1149 // Check ref
1150 const ref = schema.options.ref;
1151 if (ref != null && (ref === model.modelName || ref === model.baseModelName)) {
1152 return true;
1153 }
1154
1155 // Check refPath
1156 const refPath = schema.options.refPath;
1157 if (refPath == null) {
1158 return false;
1159 }
1160 const modelName = val.get(refPath);
1161 if (modelName === model.modelName || modelName === model.baseModelName) {
1162 return true;
1163 }
1164 return false;
1165 })();
1166
1167 let didPopulate = false;
1168 if (refMatches && val instanceof Document) {
1169 this.populated(path, val._id, { [populateModelSymbol]: val.constructor });
1170 didPopulate = true;
1171 }
1172
1173 let popOpts;
1174 if (schema.options &&
1175 Array.isArray(schema.options[this.schema.options.typeKey]) &&
1176 schema.options[this.schema.options.typeKey].length &&
1177 schema.options[this.schema.options.typeKey][0].ref &&
1178 _isManuallyPopulatedArray(val, schema.options[this.schema.options.typeKey][0].ref)) {
1179 if (this.ownerDocument) {
1180 popOpts = { [populateModelSymbol]: val[0].constructor };
1181 this.ownerDocument().populated(this.$__fullPath(path),
1182 val.map(function(v) { return v._id; }), popOpts);
1183 } else {
1184 popOpts = { [populateModelSymbol]: val[0].constructor };
1185 this.populated(path, val.map(function(v) { return v._id; }), popOpts);
1186 }
1187 didPopulate = true;
1188 }
1189
1190 if (this.schema.singleNestedPaths[path] == null) {
1191 // If this path is underneath a single nested schema, we'll call the setter
1192 // later in `$__set()` because we don't take `_doc` when we iterate through
1193 // a single nested doc. That's to make sure we get the correct context.
1194 // Otherwise we would double-call the setter, see gh-7196.
1195 val = schema.applySetters(val, this, false, priorVal);
1196 }
1197
1198 if (schema.$isMongooseDocumentArray &&
1199 Array.isArray(val) &&
1200 val.length > 0 &&
1201 val[0] != null &&
1202 val[0].$__ != null &&
1203 val[0].$__.populated != null) {
1204 const populatedPaths = Object.keys(val[0].$__.populated);
1205 for (const populatedPath of populatedPaths) {
1206 this.populated(path + '.' + populatedPath,
1207 val.map(v => v.populated(populatedPath)),
1208 val[0].$__.populated[populatedPath].options);
1209 }
1210 didPopulate = true;
1211 }
1212
1213 if (!didPopulate && this.$__.populated) {
1214 // If this array partially contains populated documents, convert them
1215 // all to ObjectIds re: #8443
1216 if (Array.isArray(val) && this.$__.populated[path]) {
1217 for (let i = 0; i < val.length; ++i) {
1218 if (val[i] instanceof Document) {
1219 val[i] = val[i]._id;
1220 }
1221 }
1222 }
1223 delete this.$__.populated[path];
1224 }
1225
1226 this.$markValid(path);
1227 } catch (e) {
1228 if (e instanceof MongooseError.StrictModeError && e.isImmutableError) {
1229 this.invalidate(path, e);
1230 } else {
1231 this.invalidate(path,
1232 new MongooseError.CastError(schema.instance, val, path, e));
1233 }
1234 shouldSet = false;
1235 }
1236
1237 if (shouldSet) {
1238 this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
1239 }
1240
1241 if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) {
1242 cleanModifiedSubpaths(this, path);
1243 }
1244
1245 return this;
1246};
1247
1248/*!
1249 * ignore
1250 */
1251
1252function _isManuallyPopulatedArray(val, ref) {
1253 if (!Array.isArray(val)) {
1254 return false;
1255 }
1256 if (val.length === 0) {
1257 return false;
1258 }
1259
1260 for (const el of val) {
1261 if (!(el instanceof Document)) {
1262 return false;
1263 }
1264 const modelName = el.constructor.modelName;
1265 if (modelName == null) {
1266 return false;
1267 }
1268 if (el.constructor.modelName != ref && el.constructor.baseModelName != ref) {
1269 return false;
1270 }
1271 }
1272
1273 return true;
1274}
1275
1276/**
1277 * Sets the value of a path, or many paths.
1278 *
1279 * ####Example:
1280 *
1281 * // path, value
1282 * doc.set(path, value)
1283 *
1284 * // object
1285 * doc.set({
1286 * path : value
1287 * , path2 : {
1288 * path : value
1289 * }
1290 * })
1291 *
1292 * // on-the-fly cast to number
1293 * doc.set(path, value, Number)
1294 *
1295 * // on-the-fly cast to string
1296 * doc.set(path, value, String)
1297 *
1298 * // changing strict mode behavior
1299 * doc.set(path, value, { strict: false });
1300 *
1301 * @param {String|Object} path path or object of key/vals to set
1302 * @param {Any} val the value to set
1303 * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
1304 * @param {Object} [options] optionally specify options that modify the behavior of the set
1305 * @api public
1306 * @method set
1307 * @memberOf Document
1308 * @instance
1309 */
1310
1311Document.prototype.set = Document.prototype.$set;
1312
1313/**
1314 * Determine if we should mark this change as modified.
1315 *
1316 * @return {Boolean}
1317 * @api private
1318 * @method $__shouldModify
1319 * @memberOf Document
1320 * @instance
1321 */
1322
1323Document.prototype.$__shouldModify = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
1324 if (this.isNew) {
1325 return true;
1326 }
1327
1328 // Re: the note about gh-7196, `val` is the raw value without casting or
1329 // setters if the full path is under a single nested subdoc because we don't
1330 // want to double run setters. So don't set it as modified. See gh-7264.
1331 if (this.schema.singleNestedPaths[path] != null) {
1332 return false;
1333 }
1334
1335 if (val === void 0 && !this.isSelected(path)) {
1336 // when a path is not selected in a query, its initial
1337 // value will be undefined.
1338 return true;
1339 }
1340
1341 if (val === void 0 && path in this.$__.activePaths.states.default) {
1342 // we're just unsetting the default value which was never saved
1343 return false;
1344 }
1345
1346 // gh-3992: if setting a populated field to a doc, don't mark modified
1347 // if they have the same _id
1348 if (this.populated(path) &&
1349 val instanceof Document &&
1350 deepEqual(val._id, priorVal)) {
1351 return false;
1352 }
1353
1354 if (!deepEqual(val, priorVal || this.get(path))) {
1355 return true;
1356 }
1357
1358 if (!constructing &&
1359 val !== null &&
1360 val !== undefined &&
1361 path in this.$__.activePaths.states.default &&
1362 deepEqual(val, schema.getDefault(this, constructing))) {
1363 // a path with a default was $unset on the server
1364 // and the user is setting it to the same value again
1365 return true;
1366 }
1367 return false;
1368};
1369
1370/**
1371 * Handles the actual setting of the value and marking the path modified if appropriate.
1372 *
1373 * @api private
1374 * @method $__set
1375 * @memberOf Document
1376 * @instance
1377 */
1378
1379Document.prototype.$__set = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
1380 Embedded = Embedded || require('./types/embedded');
1381
1382 const shouldModify = this.$__shouldModify(pathToMark, path, constructing, parts,
1383 schema, val, priorVal);
1384 const _this = this;
1385
1386 if (shouldModify) {
1387 this.markModified(pathToMark);
1388
1389 // handle directly setting arrays (gh-1126)
1390 MongooseArray || (MongooseArray = require('./types/array'));
1391 if (val && val.isMongooseArray) {
1392 val._registerAtomic('$set', val);
1393
1394 // Update embedded document parent references (gh-5189)
1395 if (val.isMongooseDocumentArray) {
1396 val.forEach(function(item) {
1397 item && item.__parentArray && (item.__parentArray = val);
1398 });
1399 }
1400
1401 // Small hack for gh-1638: if we're overwriting the entire array, ignore
1402 // paths that were modified before the array overwrite
1403 this.$__.activePaths.forEach(function(modifiedPath) {
1404 if (modifiedPath.startsWith(path + '.')) {
1405 _this.$__.activePaths.ignore(modifiedPath);
1406 }
1407 });
1408 }
1409 }
1410
1411 let obj = this._doc;
1412 let i = 0;
1413 const l = parts.length;
1414 let cur = '';
1415
1416 for (; i < l; i++) {
1417 const next = i + 1;
1418 const last = next === l;
1419 cur += (cur ? '.' + parts[i] : parts[i]);
1420 if (specialProperties.has(parts[i])) {
1421 return;
1422 }
1423
1424 if (last) {
1425 if (obj instanceof Map) {
1426 obj.set(parts[i], val);
1427 } else {
1428 obj[parts[i]] = val;
1429 }
1430 } else {
1431 if (utils.isPOJO(obj[parts[i]])) {
1432 obj = obj[parts[i]];
1433 } else if (obj[parts[i]] && obj[parts[i]] instanceof Embedded) {
1434 obj = obj[parts[i]];
1435 } else if (obj[parts[i]] && obj[parts[i]].$isSingleNested) {
1436 obj = obj[parts[i]];
1437 } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
1438 obj = obj[parts[i]];
1439 } else {
1440 obj[parts[i]] = obj[parts[i]] || {};
1441 obj = obj[parts[i]];
1442 }
1443 }
1444 }
1445};
1446
1447/**
1448 * Gets a raw value from a path (no getters)
1449 *
1450 * @param {String} path
1451 * @api private
1452 */
1453
1454Document.prototype.$__getValue = function(path) {
1455 return utils.getValue(path, this._doc);
1456};
1457
1458/**
1459 * Sets a raw value for a path (no casting, setters, transformations)
1460 *
1461 * @param {String} path
1462 * @param {Object} value
1463 * @api private
1464 */
1465
1466Document.prototype.$__setValue = function(path, val) {
1467 utils.setValue(path, val, this._doc);
1468 return this;
1469};
1470
1471/**
1472 * Returns the value of a path.
1473 *
1474 * ####Example
1475 *
1476 * // path
1477 * doc.get('age') // 47
1478 *
1479 * // dynamic casting to a string
1480 * doc.get('age', String) // "47"
1481 *
1482 * @param {String} path
1483 * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
1484 * @param {Object} [options]
1485 * @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
1486 * @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
1487 * @api public
1488 */
1489
1490Document.prototype.get = function(path, type, options) {
1491 let adhoc;
1492 options = options || {};
1493 if (type) {
1494 adhoc = this.schema.interpretAsType(path, type, this.schema.options);
1495 }
1496
1497 let schema = this.$__path(path);
1498 if (schema == null) {
1499 schema = this.schema.virtualpath(path);
1500 }
1501 if (schema instanceof MixedSchema) {
1502 const virtual = this.schema.virtualpath(path);
1503 if (virtual != null) {
1504 schema = virtual;
1505 }
1506 }
1507 const pieces = path.split('.');
1508 let obj = this._doc;
1509
1510 if (schema instanceof VirtualType) {
1511 if (schema.getters.length === 0) {
1512 return void 0;
1513 }
1514 return schema.applyGetters(null, this);
1515 }
1516
1517 // Might need to change path for top-level alias
1518 if (typeof this.schema.aliases[pieces[0]] == 'string') {
1519 pieces[0] = this.schema.aliases[pieces[0]];
1520 }
1521
1522 for (let i = 0, l = pieces.length; i < l; i++) {
1523 if (obj && obj._doc) {
1524 obj = obj._doc;
1525 }
1526
1527 if (obj == null) {
1528 obj = void 0;
1529 } else if (obj instanceof Map) {
1530 obj = obj.get(pieces[i]);
1531 } else if (i === l - 1) {
1532 obj = utils.getValue(pieces[i], obj);
1533 } else {
1534 obj = obj[pieces[i]];
1535 }
1536 }
1537
1538 if (adhoc) {
1539 obj = adhoc.cast(obj);
1540 }
1541
1542 if (schema != null && options.getters !== false) {
1543 obj = schema.applyGetters(obj, this);
1544 } else if (this.schema.nested[path] && options.virtuals) {
1545 // Might need to apply virtuals if this is a nested path
1546 return applyVirtuals(this, utils.clone(obj) || {}, { path: path });
1547 }
1548
1549 return obj;
1550};
1551
1552/*!
1553 * ignore
1554 */
1555
1556Document.prototype[getSymbol] = Document.prototype.get;
1557
1558/**
1559 * Returns the schematype for the given `path`.
1560 *
1561 * @param {String} path
1562 * @api private
1563 * @method $__path
1564 * @memberOf Document
1565 * @instance
1566 */
1567
1568Document.prototype.$__path = function(path) {
1569 const adhocs = this.$__.adhocPaths;
1570 const adhocType = adhocs && adhocs.hasOwnProperty(path) ? adhocs[path] : null;
1571
1572 if (adhocType) {
1573 return adhocType;
1574 }
1575 return this.schema.path(path);
1576};
1577
1578/**
1579 * Marks the path as having pending changes to write to the db.
1580 *
1581 * _Very helpful when using [Mixed](./schematypes.html#mixed) types._
1582 *
1583 * ####Example:
1584 *
1585 * doc.mixed.type = 'changed';
1586 * doc.markModified('mixed.type');
1587 * doc.save() // changes to mixed.type are now persisted
1588 *
1589 * @param {String} path the path to mark modified
1590 * @param {Document} [scope] the scope to run validators with
1591 * @api public
1592 */
1593
1594Document.prototype.markModified = function(path, scope) {
1595 this.$__.activePaths.modify(path);
1596 if (scope != null && !this.ownerDocument) {
1597 this.$__.pathsToScopes[path] = scope;
1598 }
1599};
1600
1601/**
1602 * Clears the modified state on the specified path.
1603 *
1604 * ####Example:
1605 *
1606 * doc.foo = 'bar';
1607 * doc.unmarkModified('foo');
1608 * doc.save(); // changes to foo will not be persisted
1609 *
1610 * @param {String} path the path to unmark modified
1611 * @api public
1612 */
1613
1614Document.prototype.unmarkModified = function(path) {
1615 this.$__.activePaths.init(path);
1616 delete this.$__.pathsToScopes[path];
1617};
1618
1619/**
1620 * Don't run validation on this path or persist changes to this path.
1621 *
1622 * ####Example:
1623 *
1624 * doc.foo = null;
1625 * doc.$ignore('foo');
1626 * doc.save(); // changes to foo will not be persisted and validators won't be run
1627 *
1628 * @memberOf Document
1629 * @instance
1630 * @method $ignore
1631 * @param {String} path the path to ignore
1632 * @api public
1633 */
1634
1635Document.prototype.$ignore = function(path) {
1636 this.$__.activePaths.ignore(path);
1637};
1638
1639/**
1640 * Returns the list of paths that have been directly modified. A direct
1641 * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
1642 * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
1643 *
1644 * A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
1645 * because a child of `a` was directly modified.
1646 *
1647 * ####Example
1648 * const schema = new Schema({ foo: String, nested: { bar: String } });
1649 * const Model = mongoose.model('Test', schema);
1650 * await Model.create({ foo: 'original', nested: { bar: 'original' } });
1651 *
1652 * const doc = await Model.findOne();
1653 * doc.nested.bar = 'modified';
1654 * doc.directModifiedPaths(); // ['nested.bar']
1655 * doc.modifiedPaths(); // ['nested', 'nested.bar']
1656 *
1657 * @return {Array}
1658 * @api public
1659 */
1660
1661Document.prototype.directModifiedPaths = function() {
1662 return Object.keys(this.$__.activePaths.states.modify);
1663};
1664
1665/**
1666 * Returns true if the given path is nullish or only contains empty objects.
1667 * Useful for determining whether this subdoc will get stripped out by the
1668 * [minimize option](/docs/guide.html#minimize).
1669 *
1670 * ####Example:
1671 * const schema = new Schema({ nested: { foo: String } });
1672 * const Model = mongoose.model('Test', schema);
1673 * const doc = new Model({});
1674 * doc.$isEmpty('nested'); // true
1675 * doc.nested.$isEmpty(); // true
1676 *
1677 * doc.nested.foo = 'bar';
1678 * doc.$isEmpty('nested'); // false
1679 * doc.nested.$isEmpty(); // false
1680 *
1681 * @memberOf Document
1682 * @instance
1683 * @api public
1684 * @method $isEmpty
1685 * @return {Boolean}
1686 */
1687
1688Document.prototype.$isEmpty = function(path) {
1689 const isEmptyOptions = {
1690 minimize: true,
1691 virtuals: false,
1692 getters: false,
1693 transform: false
1694 };
1695
1696 if (arguments.length > 0) {
1697 const v = this.get(path);
1698 if (v == null) {
1699 return true;
1700 }
1701 if (typeof v !== 'object') {
1702 return false;
1703 }
1704 if (utils.isPOJO(v)) {
1705 return _isEmpty(v);
1706 }
1707 return Object.keys(v.toObject(isEmptyOptions)).length === 0;
1708 }
1709
1710 return Object.keys(this.toObject(isEmptyOptions)).length === 0;
1711};
1712
1713function _isEmpty(v) {
1714 if (v == null) {
1715 return true;
1716 }
1717 if (typeof v !== 'object' || Array.isArray(v)) {
1718 return false;
1719 }
1720 for (const key of Object.keys(v)) {
1721 if (!_isEmpty(v[key])) {
1722 return false;
1723 }
1724 }
1725 return true;
1726}
1727
1728/**
1729 * Returns the list of paths that have been modified.
1730 *
1731 * @param {Object} [options]
1732 * @param {Boolean} [options.includeChildren=false] if true, returns children of modified paths as well. For example, if false, the list of modified paths for `doc.colors = { primary: 'blue' };` will **not** contain `colors.primary`. If true, `modifiedPaths()` will return an array that contains `colors.primary`.
1733 * @return {Array}
1734 * @api public
1735 */
1736
1737Document.prototype.modifiedPaths = function(options) {
1738 options = options || {};
1739 const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
1740 const _this = this;
1741 return directModifiedPaths.reduce(function(list, path) {
1742 const parts = path.split('.');
1743 list = list.concat(parts.reduce(function(chains, part, i) {
1744 return chains.concat(parts.slice(0, i).concat(part).join('.'));
1745 }, []).filter(function(chain) {
1746 return (list.indexOf(chain) === -1);
1747 }));
1748
1749 if (!options.includeChildren) {
1750 return list;
1751 }
1752
1753 let cur = _this.get(path);
1754 if (cur != null && typeof cur === 'object') {
1755 if (cur._doc) {
1756 cur = cur._doc;
1757 }
1758 if (Array.isArray(cur)) {
1759 const len = cur.length;
1760 for (let i = 0; i < len; ++i) {
1761 if (list.indexOf(path + '.' + i) === -1) {
1762 list.push(path + '.' + i);
1763 if (cur[i] != null && cur[i].$__) {
1764 const modified = cur[i].modifiedPaths();
1765 for (const childPath of modified) {
1766 list.push(path + '.' + i + '.' + childPath);
1767 }
1768 }
1769 }
1770 }
1771 } else {
1772 Object.keys(cur).
1773 filter(function(key) {
1774 return list.indexOf(path + '.' + key) === -1;
1775 }).
1776 forEach(function(key) {
1777 list.push(path + '.' + key);
1778 });
1779 }
1780 }
1781
1782 return list;
1783 }, []);
1784};
1785
1786/**
1787 * Returns true if this document was modified, else false.
1788 *
1789 * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
1790 *
1791 * ####Example
1792 *
1793 * doc.set('documents.0.title', 'changed');
1794 * doc.isModified() // true
1795 * doc.isModified('documents') // true
1796 * doc.isModified('documents.0.title') // true
1797 * doc.isModified('documents otherProp') // true
1798 * doc.isDirectModified('documents') // false
1799 *
1800 * @param {String} [path] optional
1801 * @return {Boolean}
1802 * @api public
1803 */
1804
1805Document.prototype.isModified = function(paths, modifiedPaths) {
1806 if (paths) {
1807 if (!Array.isArray(paths)) {
1808 paths = paths.split(' ');
1809 }
1810 const modified = modifiedPaths || this.modifiedPaths();
1811 const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
1812 const isModifiedChild = paths.some(function(path) {
1813 return !!~modified.indexOf(path);
1814 });
1815 return isModifiedChild || paths.some(function(path) {
1816 return directModifiedPaths.some(function(mod) {
1817 return mod === path || path.startsWith(mod + '.');
1818 });
1819 });
1820 }
1821 return this.$__.activePaths.some('modify');
1822};
1823
1824/**
1825 * Checks if a path is set to its default.
1826 *
1827 * ####Example
1828 *
1829 * MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
1830 * var m = new MyModel();
1831 * m.$isDefault('name'); // true
1832 *
1833 * @memberOf Document
1834 * @instance
1835 * @method $isDefault
1836 * @param {String} [path]
1837 * @return {Boolean}
1838 * @api public
1839 */
1840
1841Document.prototype.$isDefault = function(path) {
1842 return (path in this.$__.activePaths.states.default);
1843};
1844
1845/**
1846 * Getter/setter, determines whether the document was removed or not.
1847 *
1848 * ####Example:
1849 * product.remove(function (err, product) {
1850 * product.$isDeleted(); // true
1851 * product.remove(); // no-op, doesn't send anything to the db
1852 *
1853 * product.$isDeleted(false);
1854 * product.$isDeleted(); // false
1855 * product.remove(); // will execute a remove against the db
1856 * })
1857 *
1858 * @param {Boolean} [val] optional, overrides whether mongoose thinks the doc is deleted
1859 * @return {Boolean} whether mongoose thinks this doc is deleted.
1860 * @method $isDeleted
1861 * @memberOf Document
1862 * @instance
1863 * @api public
1864 */
1865
1866Document.prototype.$isDeleted = function(val) {
1867 if (arguments.length === 0) {
1868 return !!this.$__.isDeleted;
1869 }
1870
1871 this.$__.isDeleted = !!val;
1872 return this;
1873};
1874
1875/**
1876 * Returns true if `path` was directly set and modified, else false.
1877 *
1878 * ####Example
1879 *
1880 * doc.set('documents.0.title', 'changed');
1881 * doc.isDirectModified('documents.0.title') // true
1882 * doc.isDirectModified('documents') // false
1883 *
1884 * @param {String} path
1885 * @return {Boolean}
1886 * @api public
1887 */
1888
1889Document.prototype.isDirectModified = function(path) {
1890 return (path in this.$__.activePaths.states.modify);
1891};
1892
1893/**
1894 * Checks if `path` was initialized.
1895 *
1896 * @param {String} path
1897 * @return {Boolean}
1898 * @api public
1899 */
1900
1901Document.prototype.isInit = function(path) {
1902 return (path in this.$__.activePaths.states.init);
1903};
1904
1905/**
1906 * Checks if `path` was selected in the source query which initialized this document.
1907 *
1908 * ####Example
1909 *
1910 * Thing.findOne().select('name').exec(function (err, doc) {
1911 * doc.isSelected('name') // true
1912 * doc.isSelected('age') // false
1913 * })
1914 *
1915 * @param {String} path
1916 * @return {Boolean}
1917 * @api public
1918 */
1919
1920Document.prototype.isSelected = function isSelected(path) {
1921 if (this.$__.selected) {
1922 if (path === '_id') {
1923 return this.$__.selected._id !== 0;
1924 }
1925
1926 const paths = Object.keys(this.$__.selected);
1927 let i = paths.length;
1928 let inclusive = null;
1929 let cur;
1930
1931 if (i === 1 && paths[0] === '_id') {
1932 // only _id was selected.
1933 return this.$__.selected._id === 0;
1934 }
1935
1936 while (i--) {
1937 cur = paths[i];
1938 if (cur === '_id') {
1939 continue;
1940 }
1941 if (!isDefiningProjection(this.$__.selected[cur])) {
1942 continue;
1943 }
1944 inclusive = !!this.$__.selected[cur];
1945 break;
1946 }
1947
1948 if (inclusive === null) {
1949 return true;
1950 }
1951
1952 if (path in this.$__.selected) {
1953 return inclusive;
1954 }
1955
1956 i = paths.length;
1957 const pathDot = path + '.';
1958
1959 while (i--) {
1960 cur = paths[i];
1961 if (cur === '_id') {
1962 continue;
1963 }
1964
1965 if (cur.startsWith(pathDot)) {
1966 return inclusive || cur !== pathDot;
1967 }
1968
1969 if (pathDot.startsWith(cur + '.')) {
1970 return inclusive;
1971 }
1972 }
1973
1974 return !inclusive;
1975 }
1976
1977 return true;
1978};
1979
1980/**
1981 * Checks if `path` was explicitly selected. If no projection, always returns
1982 * true.
1983 *
1984 * ####Example
1985 *
1986 * Thing.findOne().select('nested.name').exec(function (err, doc) {
1987 * doc.isDirectSelected('nested.name') // true
1988 * doc.isDirectSelected('nested.otherName') // false
1989 * doc.isDirectSelected('nested') // false
1990 * })
1991 *
1992 * @param {String} path
1993 * @return {Boolean}
1994 * @api public
1995 */
1996
1997Document.prototype.isDirectSelected = function isDirectSelected(path) {
1998 if (this.$__.selected) {
1999 if (path === '_id') {
2000 return this.$__.selected._id !== 0;
2001 }
2002
2003 const paths = Object.keys(this.$__.selected);
2004 let i = paths.length;
2005 let inclusive = null;
2006 let cur;
2007
2008 if (i === 1 && paths[0] === '_id') {
2009 // only _id was selected.
2010 return this.$__.selected._id === 0;
2011 }
2012
2013 while (i--) {
2014 cur = paths[i];
2015 if (cur === '_id') {
2016 continue;
2017 }
2018 if (!isDefiningProjection(this.$__.selected[cur])) {
2019 continue;
2020 }
2021 inclusive = !!this.$__.selected[cur];
2022 break;
2023 }
2024
2025 if (inclusive === null) {
2026 return true;
2027 }
2028
2029 if (path in this.$__.selected) {
2030 return inclusive;
2031 }
2032
2033 return !inclusive;
2034 }
2035
2036 return true;
2037};
2038
2039/**
2040 * Executes registered validation rules for this document.
2041 *
2042 * ####Note:
2043 *
2044 * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`.
2045 *
2046 * ####Example:
2047 *
2048 * doc.validate(function (err) {
2049 * if (err) handleError(err);
2050 * else // validation passed
2051 * });
2052 *
2053 * @param {Array|String} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list.
2054 * @param {Object} [options] internal options
2055 * @param {Function} [callback] optional callback called after validation completes, passing an error if one occurred
2056 * @return {Promise} Promise
2057 * @api public
2058 */
2059
2060Document.prototype.validate = function(pathsToValidate, options, callback) {
2061 let parallelValidate;
2062 this.$op = 'validate';
2063
2064 if (this.ownerDocument != null) {
2065 // Skip parallel validate check for subdocuments
2066 } else if (this.$__.validating) {
2067 parallelValidate = new ParallelValidateError(this, {
2068 parentStack: options && options.parentStack,
2069 conflictStack: this.$__.validating.stack
2070 });
2071 } else {
2072 this.$__.validating = new ParallelValidateError(this, {parentStack: options && options.parentStack});
2073 }
2074
2075 if (typeof pathsToValidate === 'function') {
2076 callback = pathsToValidate;
2077 options = null;
2078 pathsToValidate = null;
2079 } else if (typeof options === 'function') {
2080 callback = options;
2081 options = pathsToValidate;
2082 pathsToValidate = null;
2083 }
2084
2085 return promiseOrCallback(callback, cb => {
2086 if (parallelValidate != null) {
2087 return cb(parallelValidate);
2088 }
2089
2090 this.$__validate(pathsToValidate, options, (error) => {
2091 this.$op = null;
2092 cb(error);
2093 });
2094 }, this.constructor.events);
2095};
2096
2097/*!
2098 * ignore
2099 */
2100
2101function _evaluateRequiredFunctions(doc) {
2102 Object.keys(doc.$__.activePaths.states.require).forEach(path => {
2103 const p = doc.schema.path(path);
2104
2105 if (p != null && typeof p.originalRequiredValue === 'function') {
2106 doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc);
2107 }
2108 });
2109}
2110
2111/*!
2112 * ignore
2113 */
2114
2115function _getPathsToValidate(doc) {
2116 const skipSchemaValidators = {};
2117
2118 _evaluateRequiredFunctions(doc);
2119
2120 // only validate required fields when necessary
2121 let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
2122 if (!doc.isSelected(path) && !doc.isModified(path)) {
2123 return false;
2124 }
2125 if (path in doc.$__.cachedRequired) {
2126 return doc.$__.cachedRequired[path];
2127 }
2128 return true;
2129 }));
2130
2131
2132 function addToPaths(p) { paths.add(p); }
2133 Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths);
2134 Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths);
2135 Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths);
2136
2137 const subdocs = doc.$__getAllSubdocs();
2138 const modifiedPaths = doc.modifiedPaths();
2139 for (const subdoc of subdocs) {
2140 if (subdoc.$basePath) {
2141 // Remove child paths for now, because we'll be validating the whole
2142 // subdoc
2143 for (const p of paths) {
2144 if (p === null || p.startsWith(subdoc.$basePath + '.')) {
2145 paths.delete(p);
2146 }
2147 }
2148
2149 if (doc.isModified(subdoc.$basePath, modifiedPaths) &&
2150 !doc.isDirectModified(subdoc.$basePath) &&
2151 !doc.$isDefault(subdoc.$basePath)) {
2152 paths.add(subdoc.$basePath);
2153
2154 skipSchemaValidators[subdoc.$basePath] = true;
2155 }
2156 }
2157 }
2158
2159 // from here on we're not removing items from paths
2160
2161 // gh-661: if a whole array is modified, make sure to run validation on all
2162 // the children as well
2163 for (const path of paths) {
2164 const _pathType = doc.schema.path(path);
2165 if (!_pathType ||
2166 !_pathType.$isMongooseArray ||
2167 // To avoid potential performance issues, skip doc arrays whose children
2168 // are not required. `getPositionalPathType()` may be slow, so avoid
2169 // it unless we have a case of #6364
2170 (_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) {
2171 continue;
2172 }
2173
2174 const val = doc.$__getValue(path);
2175 _pushNestedArrayPaths(val, paths, path);
2176 }
2177
2178 function _pushNestedArrayPaths(val, paths, path) {
2179 if (val != null) {
2180 const numElements = val.length;
2181 for (let j = 0; j < numElements; ++j) {
2182 if (Array.isArray(val[j])) {
2183 _pushNestedArrayPaths(val[j], paths, path + '.' + j);
2184 } else {
2185 paths.add(path + '.' + j);
2186 }
2187 }
2188 }
2189 }
2190
2191 const flattenOptions = { skipArrays: true };
2192 for (const pathToCheck of paths) {
2193 if (doc.schema.nested[pathToCheck]) {
2194 let _v = doc.$__getValue(pathToCheck);
2195 if (isMongooseObject(_v)) {
2196 _v = _v.toObject({ transform: false });
2197 }
2198 const flat = flatten(_v, pathToCheck, flattenOptions, doc.schema);
2199 Object.keys(flat).forEach(addToPaths);
2200 }
2201 }
2202
2203
2204 for (const path of paths) {
2205 // Single nested paths (paths embedded under single nested subdocs) will
2206 // be validated on their own when we call `validate()` on the subdoc itself.
2207 // Re: gh-8468
2208 if (doc.schema.singleNestedPaths.hasOwnProperty(path)) {
2209 paths.delete(path);
2210 continue;
2211 }
2212 const _pathType = doc.schema.path(path);
2213 if (!_pathType || !_pathType.$isSchemaMap) {
2214 continue;
2215 }
2216
2217 const val = doc.$__getValue(path);
2218 if (val == null) {
2219 continue;
2220 }
2221 for (const key of val.keys()) {
2222 paths.add(path + '.' + key);
2223 }
2224 }
2225
2226 paths = Array.from(paths);
2227 return [paths, skipSchemaValidators];
2228}
2229
2230/*!
2231 * ignore
2232 */
2233
2234Document.prototype.$__validate = function(pathsToValidate, options, callback) {
2235 if (typeof pathsToValidate === 'function') {
2236 callback = pathsToValidate;
2237 options = null;
2238 pathsToValidate = null;
2239 } else if (typeof options === 'function') {
2240 callback = options;
2241 options = null;
2242 }
2243
2244 const hasValidateModifiedOnlyOption = options &&
2245 (typeof options === 'object') &&
2246 ('validateModifiedOnly' in options);
2247
2248 let shouldValidateModifiedOnly;
2249 if (hasValidateModifiedOnlyOption) {
2250 shouldValidateModifiedOnly = !!options.validateModifiedOnly;
2251 } else {
2252 shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
2253 }
2254
2255 const _this = this;
2256 const _complete = () => {
2257 let validationError = this.$__.validationError;
2258 this.$__.validationError = undefined;
2259
2260 if (shouldValidateModifiedOnly && validationError != null) {
2261 // Remove any validation errors that aren't from modified paths
2262 const errors = Object.keys(validationError.errors);
2263 for (const errPath of errors) {
2264 if (!this.isModified(errPath)) {
2265 delete validationError.errors[errPath];
2266 }
2267 }
2268 if (Object.keys(validationError.errors).length === 0) {
2269 validationError = void 0;
2270 }
2271 }
2272
2273 this.$__.cachedRequired = {};
2274 this.emit('validate', _this);
2275 this.constructor.emit('validate', _this);
2276
2277 this.$__.validating = null;
2278 if (validationError) {
2279 for (const key in validationError.errors) {
2280 // Make sure cast errors persist
2281 if (!this[documentArrayParent] &&
2282 validationError.errors[key] instanceof MongooseError.CastError) {
2283 this.invalidate(key, validationError.errors[key]);
2284 }
2285 }
2286
2287 return validationError;
2288 }
2289 };
2290
2291 // only validate required fields when necessary
2292 const pathDetails = _getPathsToValidate(this);
2293 let paths = shouldValidateModifiedOnly ?
2294 pathDetails[0].filter((path) => this.isModified(path)) :
2295 pathDetails[0];
2296 const skipSchemaValidators = pathDetails[1];
2297
2298 if (Array.isArray(pathsToValidate)) {
2299 const _pathsToValidate = new Set(pathsToValidate);
2300 paths = paths.filter(p => _pathsToValidate.has(p));
2301 }
2302
2303 if (paths.length === 0) {
2304 return process.nextTick(function() {
2305 const error = _complete();
2306 if (error) {
2307 return _this.schema.s.hooks.execPost('validate:error', _this, [ _this], { error: error }, function(error) {
2308 callback(error);
2309 });
2310 }
2311 callback(null, _this);
2312 });
2313 }
2314
2315 const validated = {};
2316 let total = 0;
2317
2318 const complete = function() {
2319 const error = _complete();
2320 if (error) {
2321 return _this.schema.s.hooks.execPost('validate:error', _this, [ _this], { error: error }, function(error) {
2322 callback(error);
2323 });
2324 }
2325 callback(null, _this);
2326 };
2327
2328 const validatePath = function(path) {
2329 if (path == null || validated[path]) {
2330 return;
2331 }
2332
2333 validated[path] = true;
2334 total++;
2335
2336 process.nextTick(function() {
2337 const p = _this.schema.path(path);
2338
2339 if (!p) {
2340 return --total || complete();
2341 }
2342
2343 // If user marked as invalid or there was a cast error, don't validate
2344 if (!_this.$isValid(path)) {
2345 --total || complete();
2346 return;
2347 }
2348
2349 let val = _this.$__getValue(path);
2350
2351 // If you `populate()` and get back a null value, required validators
2352 // shouldn't fail (gh-8018). We should always fall back to the populated
2353 // value.
2354 let pop;
2355 if (val == null && (pop = _this.populated(path))) {
2356 val = pop;
2357 }
2358 const scope = path in _this.$__.pathsToScopes ?
2359 _this.$__.pathsToScopes[path] :
2360 _this;
2361
2362 const doValidateOptions = {
2363 skipSchemaValidators: skipSchemaValidators[path],
2364 path: path
2365 };
2366 p.doValidate(val, function(err) {
2367 if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
2368 if (p.$isSingleNested &&
2369 err instanceof ValidationError &&
2370 p.schema.options.storeSubdocValidationError === false) {
2371 return --total || complete();
2372 }
2373 _this.invalidate(path, err, undefined, true);
2374 }
2375 --total || complete();
2376 }, scope, doValidateOptions);
2377 });
2378 };
2379
2380 const numPaths = paths.length;
2381 for (let i = 0; i < numPaths; ++i) {
2382 validatePath(paths[i]);
2383 }
2384};
2385
2386/**
2387 * Executes registered validation rules (skipping asynchronous validators) for this document.
2388 *
2389 * ####Note:
2390 *
2391 * This method is useful if you need synchronous validation.
2392 *
2393 * ####Example:
2394 *
2395 * var err = doc.validateSync();
2396 * if (err) {
2397 * handleError(err);
2398 * } else {
2399 * // validation passed
2400 * }
2401 *
2402 * @param {Array|string} pathsToValidate only validate the given paths
2403 * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
2404 * @api public
2405 */
2406
2407Document.prototype.validateSync = function(pathsToValidate, options) {
2408 const _this = this;
2409
2410 const hasValidateModifiedOnlyOption = options &&
2411 (typeof options === 'object') &&
2412 ('validateModifiedOnly' in options);
2413
2414 let shouldValidateModifiedOnly;
2415 if (hasValidateModifiedOnlyOption) {
2416 shouldValidateModifiedOnly = !!options.validateModifiedOnly;
2417 } else {
2418 shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
2419 }
2420
2421 if (typeof pathsToValidate === 'string') {
2422 pathsToValidate = pathsToValidate.split(' ');
2423 }
2424
2425 // only validate required fields when necessary
2426 const pathDetails = _getPathsToValidate(this);
2427 let paths = shouldValidateModifiedOnly ?
2428 pathDetails[0].filter((path) => this.isModified(path)) :
2429 pathDetails[0];
2430 const skipSchemaValidators = pathDetails[1];
2431
2432 if (pathsToValidate && pathsToValidate.length) {
2433 const tmp = [];
2434 for (let i = 0; i < paths.length; ++i) {
2435 if (pathsToValidate.indexOf(paths[i]) !== -1) {
2436 tmp.push(paths[i]);
2437 }
2438 }
2439 paths = tmp;
2440 }
2441
2442 const validating = {};
2443
2444 paths.forEach(function(path) {
2445 if (validating[path]) {
2446 return;
2447 }
2448
2449 validating[path] = true;
2450
2451 const p = _this.schema.path(path);
2452 if (!p) {
2453 return;
2454 }
2455 if (!_this.$isValid(path)) {
2456 return;
2457 }
2458
2459 const val = _this.$__getValue(path);
2460 const err = p.doValidateSync(val, _this, {
2461 skipSchemaValidators: skipSchemaValidators[path],
2462 path: path
2463 });
2464 if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
2465 if (p.$isSingleNested &&
2466 err instanceof ValidationError &&
2467 p.schema.options.storeSubdocValidationError === false) {
2468 return;
2469 }
2470 _this.invalidate(path, err, undefined, true);
2471 }
2472 });
2473
2474 const err = _this.$__.validationError;
2475 _this.$__.validationError = undefined;
2476 _this.emit('validate', _this);
2477 _this.constructor.emit('validate', _this);
2478
2479 if (err) {
2480 for (const key in err.errors) {
2481 // Make sure cast errors persist
2482 if (err.errors[key] instanceof MongooseError.CastError) {
2483 _this.invalidate(key, err.errors[key]);
2484 }
2485 }
2486 }
2487
2488 return err;
2489};
2490
2491/**
2492 * Marks a path as invalid, causing validation to fail.
2493 *
2494 * The `errorMsg` argument will become the message of the `ValidationError`.
2495 *
2496 * The `value` argument (if passed) will be available through the `ValidationError.value` property.
2497 *
2498 * doc.invalidate('size', 'must be less than 20', 14);
2499
2500 * doc.validate(function (err) {
2501 * console.log(err)
2502 * // prints
2503 * { message: 'Validation failed',
2504 * name: 'ValidationError',
2505 * errors:
2506 * { size:
2507 * { message: 'must be less than 20',
2508 * name: 'ValidatorError',
2509 * path: 'size',
2510 * type: 'user defined',
2511 * value: 14 } } }
2512 * })
2513 *
2514 * @param {String} path the field to invalidate
2515 * @param {String|Error} errorMsg the error which states the reason `path` was invalid
2516 * @param {Object|String|Number|any} value optional invalid value
2517 * @param {String} [kind] optional `kind` property for the error
2518 * @return {ValidationError} the current ValidationError, with all currently invalidated paths
2519 * @api public
2520 */
2521
2522Document.prototype.invalidate = function(path, err, val, kind) {
2523 if (!this.$__.validationError) {
2524 this.$__.validationError = new ValidationError(this);
2525 }
2526
2527 if (this.$__.validationError.errors[path]) {
2528 return;
2529 }
2530
2531 if (!err || typeof err === 'string') {
2532 err = new ValidatorError({
2533 path: path,
2534 message: err,
2535 type: kind || 'user defined',
2536 value: val
2537 });
2538 }
2539
2540 if (this.$__.validationError === err) {
2541 return this.$__.validationError;
2542 }
2543
2544 this.$__.validationError.addError(path, err);
2545 return this.$__.validationError;
2546};
2547
2548/**
2549 * Marks a path as valid, removing existing validation errors.
2550 *
2551 * @param {String} path the field to mark as valid
2552 * @api public
2553 * @memberOf Document
2554 * @instance
2555 * @method $markValid
2556 */
2557
2558Document.prototype.$markValid = function(path) {
2559 if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
2560 return;
2561 }
2562
2563 delete this.$__.validationError.errors[path];
2564 if (Object.keys(this.$__.validationError.errors).length === 0) {
2565 this.$__.validationError = null;
2566 }
2567};
2568
2569/**
2570 * Saves this document.
2571 *
2572 * ####Example:
2573 *
2574 * product.sold = Date.now();
2575 * product.save(function (err, product) {
2576 * if (err) ..
2577 * })
2578 *
2579 * The callback will receive two parameters
2580 *
2581 * 1. `err` if an error occurred
2582 * 2. `product` which is the saved `product`
2583 *
2584 * As an extra measure of flow control, save will return a Promise.
2585 * ####Example:
2586 * product.save().then(function(product) {
2587 * ...
2588 * });
2589 *
2590 * @param {Object} [options] options optional options
2591 * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe)
2592 * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
2593 * @param {Function} [fn] optional callback
2594 * @method save
2595 * @memberOf Document
2596 * @instance
2597 * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
2598 * @api public
2599 * @see middleware http://mongoosejs.com/docs/middleware.html
2600 */
2601
2602/**
2603 * Checks if a path is invalid
2604 *
2605 * @param {String} path the field to check
2606 * @method $isValid
2607 * @memberOf Document
2608 * @instance
2609 * @api private
2610 */
2611
2612Document.prototype.$isValid = function(path) {
2613 return !this.$__.validationError || !this.$__.validationError.errors[path];
2614};
2615
2616/**
2617 * Resets the internal modified state of this document.
2618 *
2619 * @api private
2620 * @return {Document}
2621 * @method $__reset
2622 * @memberOf Document
2623 * @instance
2624 */
2625
2626Document.prototype.$__reset = function reset() {
2627 let _this = this;
2628 DocumentArray || (DocumentArray = require('./types/documentarray'));
2629
2630 this.$__.activePaths
2631 .map('init', 'modify', function(i) {
2632 return _this.$__getValue(i);
2633 })
2634 .filter(function(val) {
2635 return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
2636 })
2637 .forEach(function(array) {
2638 let i = array.length;
2639 while (i--) {
2640 const doc = array[i];
2641 if (!doc) {
2642 continue;
2643 }
2644 doc.$__reset();
2645 }
2646
2647 _this.$__.activePaths.init(array.$path());
2648
2649 array[arrayAtomicsSymbol] = {};
2650 });
2651
2652 this.$__.activePaths.
2653 map('init', 'modify', function(i) {
2654 return _this.$__getValue(i);
2655 }).
2656 filter(function(val) {
2657 return val && val.$isSingleNested;
2658 }).
2659 forEach(function(doc) {
2660 doc.$__reset();
2661 _this.$__.activePaths.init(doc.$basePath);
2662 });
2663
2664 // clear atomics
2665 this.$__dirty().forEach(function(dirt) {
2666 const type = dirt.value;
2667
2668 if (type && type[arrayAtomicsSymbol]) {
2669 type[arrayAtomicsSymbol] = {};
2670 }
2671 });
2672
2673 // Clear 'dirty' cache
2674 this.$__.activePaths.clear('modify');
2675 this.$__.activePaths.clear('default');
2676 this.$__.validationError = undefined;
2677 this.errors = undefined;
2678 _this = this;
2679 this.schema.requiredPaths().forEach(function(path) {
2680 _this.$__.activePaths.require(path);
2681 });
2682
2683 return this;
2684};
2685
2686/**
2687 * Returns this documents dirty paths / vals.
2688 *
2689 * @api private
2690 * @method $__dirty
2691 * @memberOf Document
2692 * @instance
2693 */
2694
2695Document.prototype.$__dirty = function() {
2696 const _this = this;
2697
2698 let all = this.$__.activePaths.map('modify', function(path) {
2699 return {
2700 path: path,
2701 value: _this.$__getValue(path),
2702 schema: _this.$__path(path)
2703 };
2704 });
2705
2706 // gh-2558: if we had to set a default and the value is not undefined,
2707 // we have to save as well
2708 all = all.concat(this.$__.activePaths.map('default', function(path) {
2709 if (path === '_id' || _this.$__getValue(path) == null) {
2710 return;
2711 }
2712 return {
2713 path: path,
2714 value: _this.$__getValue(path),
2715 schema: _this.$__path(path)
2716 };
2717 }));
2718
2719 // Sort dirty paths in a flat hierarchy.
2720 all.sort(function(a, b) {
2721 return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
2722 });
2723
2724 // Ignore "foo.a" if "foo" is dirty already.
2725 const minimal = [];
2726 let lastPath;
2727 let top;
2728
2729 all.forEach(function(item) {
2730 if (!item) {
2731 return;
2732 }
2733 if (lastPath == null || item.path.indexOf(lastPath) !== 0) {
2734 lastPath = item.path + '.';
2735 minimal.push(item);
2736 top = item;
2737 } else if (top != null &&
2738 top.value != null &&
2739 top.value[arrayAtomicsSymbol] != null &&
2740 top.value.hasAtomics()) {
2741 // special case for top level MongooseArrays
2742 // the `top` array itself and a sub path of `top` are being modified.
2743 // the only way to honor all of both modifications is through a $set
2744 // of entire array.
2745 top.value[arrayAtomicsSymbol] = {};
2746 top.value[arrayAtomicsSymbol].$set = top.value;
2747 }
2748 });
2749
2750 top = lastPath = null;
2751 return minimal;
2752};
2753
2754/**
2755 * Assigns/compiles `schema` into this documents prototype.
2756 *
2757 * @param {Schema} schema
2758 * @api private
2759 * @method $__setSchema
2760 * @memberOf Document
2761 * @instance
2762 */
2763
2764Document.prototype.$__setSchema = function(schema) {
2765 schema.plugin(idGetter, { deduplicate: true });
2766 compile(schema.tree, this, undefined, schema.options);
2767
2768 // Apply default getters if virtual doesn't have any (gh-6262)
2769 for (const key of Object.keys(schema.virtuals)) {
2770 schema.virtuals[key]._applyDefaultGetters();
2771 }
2772
2773 this.schema = schema;
2774 this[documentSchemaSymbol] = schema;
2775};
2776
2777
2778/**
2779 * Get active path that were changed and are arrays
2780 *
2781 * @api private
2782 * @method $__getArrayPathsToValidate
2783 * @memberOf Document
2784 * @instance
2785 */
2786
2787Document.prototype.$__getArrayPathsToValidate = function() {
2788 DocumentArray || (DocumentArray = require('./types/documentarray'));
2789
2790 // validate all document arrays.
2791 return this.$__.activePaths
2792 .map('init', 'modify', function(i) {
2793 return this.$__getValue(i);
2794 }.bind(this))
2795 .filter(function(val) {
2796 return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
2797 }).reduce(function(seed, array) {
2798 return seed.concat(array);
2799 }, [])
2800 .filter(function(doc) {
2801 return doc;
2802 });
2803};
2804
2805
2806/**
2807 * Get all subdocs (by bfs)
2808 *
2809 * @api private
2810 * @method $__getAllSubdocs
2811 * @memberOf Document
2812 * @instance
2813 */
2814
2815Document.prototype.$__getAllSubdocs = function() {
2816 DocumentArray || (DocumentArray = require('./types/documentarray'));
2817 Embedded = Embedded || require('./types/embedded');
2818
2819 function docReducer(doc, seed, path) {
2820 let val = doc;
2821 if (path) {
2822 if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) {
2823 val = doc._doc[path];
2824 } else {
2825 val = doc[path];
2826 }
2827 }
2828 if (val instanceof Embedded) {
2829 seed.push(val);
2830 } else if (val instanceof Map) {
2831 seed = Array.from(val.keys()).reduce(function(seed, path) {
2832 return docReducer(val.get(path), seed, null);
2833 }, seed);
2834 } else if (val && val.$isSingleNested) {
2835 seed = Object.keys(val._doc).reduce(function(seed, path) {
2836 return docReducer(val._doc, seed, path);
2837 }, seed);
2838 seed.push(val);
2839 } else if (val && val.isMongooseDocumentArray) {
2840 val.forEach(function _docReduce(doc) {
2841 if (!doc || !doc._doc) {
2842 return;
2843 }
2844 seed = Object.keys(doc._doc).reduce(function(seed, path) {
2845 return docReducer(doc._doc, seed, path);
2846 }, seed);
2847 if (doc instanceof Embedded) {
2848 seed.push(doc);
2849 }
2850 });
2851 } else if (val instanceof Document && val.$__isNested) {
2852 seed = Object.keys(val).reduce(function(seed, path) {
2853 return docReducer(val, seed, path);
2854 }, seed);
2855 }
2856 return seed;
2857 }
2858
2859 const _this = this;
2860 const subDocs = Object.keys(this._doc).reduce(function(seed, path) {
2861 return docReducer(_this, seed, path);
2862 }, []);
2863
2864 return subDocs;
2865};
2866
2867/*!
2868 * Runs queued functions
2869 */
2870
2871function applyQueue(doc) {
2872 const q = doc.schema && doc.schema.callQueue;
2873 if (!q.length) {
2874 return;
2875 }
2876 let pair;
2877
2878 for (let i = 0; i < q.length; ++i) {
2879 pair = q[i];
2880 if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
2881 doc[pair[0]].apply(doc, pair[1]);
2882 }
2883 }
2884}
2885
2886/*!
2887 * ignore
2888 */
2889
2890Document.prototype.$__handleReject = function handleReject(err) {
2891 // emit on the Model if listening
2892 if (this.listeners('error').length) {
2893 this.emit('error', err);
2894 } else if (this.constructor.listeners && this.constructor.listeners('error').length) {
2895 this.constructor.emit('error', err);
2896 } else if (this.listeners && this.listeners('error').length) {
2897 this.emit('error', err);
2898 }
2899};
2900
2901/**
2902 * Internal helper for toObject() and toJSON() that doesn't manipulate options
2903 *
2904 * @api private
2905 * @method $toObject
2906 * @memberOf Document
2907 * @instance
2908 */
2909
2910Document.prototype.$toObject = function(options, json) {
2911 let defaultOptions = {
2912 transform: true,
2913 flattenDecimals: true
2914 };
2915
2916 const path = json ? 'toJSON' : 'toObject';
2917 const baseOptions = get(this, 'constructor.base.options.' + path, {});
2918 const schemaOptions = get(this, 'schema.options', {});
2919 // merge base default options with Schema's set default options if available.
2920 // `clone` is necessary here because `utils.options` directly modifies the second input.
2921 defaultOptions = utils.options(defaultOptions, clone(baseOptions));
2922 defaultOptions = utils.options(defaultOptions, clone(schemaOptions[path] || {}));
2923
2924 // If options do not exist or is not an object, set it to empty object
2925 options = utils.isPOJO(options) ? clone(options) : {};
2926
2927 if (!('flattenMaps' in options)) {
2928 options.flattenMaps = defaultOptions.flattenMaps;
2929 }
2930
2931 let _minimize;
2932 if (options.minimize != null) {
2933 _minimize = options.minimize;
2934 } else if (defaultOptions.minimize != null) {
2935 _minimize = defaultOptions.minimize;
2936 } else {
2937 _minimize = schemaOptions.minimize;
2938 }
2939
2940 // The original options that will be passed to `clone()`. Important because
2941 // `clone()` will recursively call `$toObject()` on embedded docs, so we
2942 // need the original options the user passed in, plus `_isNested` and
2943 // `_parentOptions` for checking whether we need to depopulate.
2944 const cloneOptions = Object.assign(utils.clone(options), {
2945 _isNested: true,
2946 json: json,
2947 minimize: _minimize
2948 });
2949
2950 if (utils.hasUserDefinedProperty(options, 'getters')) {
2951 cloneOptions.getters = options.getters;
2952 }
2953 if (utils.hasUserDefinedProperty(options, 'virtuals')) {
2954 cloneOptions.virtuals = options.virtuals;
2955 }
2956
2957 const depopulate = options.depopulate ||
2958 get(options, '_parentOptions.depopulate', false);
2959 // _isNested will only be true if this is not the top level document, we
2960 // should never depopulate
2961 if (depopulate && options._isNested && this.$__.wasPopulated) {
2962 // populated paths that we set to a document
2963 return clone(this._id, cloneOptions);
2964 }
2965
2966 // merge default options with input options.
2967 options = utils.options(defaultOptions, options);
2968 options._isNested = true;
2969 options.json = json;
2970 options.minimize = _minimize;
2971
2972 cloneOptions._parentOptions = options;
2973 cloneOptions._skipSingleNestedGetters = true;
2974
2975 const gettersOptions = Object.assign({}, cloneOptions);
2976 gettersOptions._skipSingleNestedGetters = false;
2977
2978 // remember the root transform function
2979 // to save it from being overwritten by sub-transform functions
2980 const originalTransform = options.transform;
2981
2982 let ret = clone(this._doc, cloneOptions) || {};
2983
2984 if (options.getters) {
2985 applyGetters(this, ret, gettersOptions);
2986
2987 if (options.minimize) {
2988 ret = minimize(ret) || {};
2989 }
2990 }
2991
2992 if (options.virtuals || (options.getters && options.virtuals !== false)) {
2993 applyVirtuals(this, ret, gettersOptions, options);
2994 }
2995
2996 if (options.versionKey === false && this.schema.options.versionKey) {
2997 delete ret[this.schema.options.versionKey];
2998 }
2999
3000 let transform = options.transform;
3001
3002 // In the case where a subdocument has its own transform function, we need to
3003 // check and see if the parent has a transform (options.transform) and if the
3004 // child schema has a transform (this.schema.options.toObject) In this case,
3005 // we need to adjust options.transform to be the child schema's transform and
3006 // not the parent schema's
3007 if (transform) {
3008 applySchemaTypeTransforms(this, ret, gettersOptions, options);
3009 }
3010
3011 if (transform === true || (schemaOptions.toObject && transform)) {
3012 const opts = options.json ? schemaOptions.toJSON : schemaOptions.toObject;
3013
3014 if (opts) {
3015 transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
3016 }
3017 } else {
3018 options.transform = originalTransform;
3019 }
3020
3021 if (typeof transform === 'function') {
3022 const xformed = transform(this, ret, options);
3023 if (typeof xformed !== 'undefined') {
3024 ret = xformed;
3025 }
3026 }
3027
3028 return ret;
3029};
3030
3031/**
3032 * Converts this document into a plain javascript object, ready for storage in MongoDB.
3033 *
3034 * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
3035 *
3036 * ####Options:
3037 *
3038 * - `getters` apply all getters (path and virtual getters), defaults to false
3039 * - `virtuals` apply virtual getters (can override `getters` option), defaults to false
3040 * - `minimize` remove empty objects (defaults to true)
3041 * - `transform` a transform function to apply to the resulting document before returning
3042 * - `depopulate` depopulate any populated paths, replacing them with their original refs (defaults to false)
3043 * - `versionKey` whether to include the version key (defaults to true)
3044 *
3045 * ####Getters/Virtuals
3046 *
3047 * Example of only applying path getters
3048 *
3049 * doc.toObject({ getters: true, virtuals: false })
3050 *
3051 * Example of only applying virtual getters
3052 *
3053 * doc.toObject({ virtuals: true })
3054 *
3055 * Example of applying both path and virtual getters
3056 *
3057 * doc.toObject({ getters: true })
3058 *
3059 * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument.
3060 *
3061 * schema.set('toObject', { virtuals: true })
3062 *
3063 * ####Transform
3064 *
3065 * We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function.
3066 *
3067 * Transform functions receive three arguments
3068 *
3069 * function (doc, ret, options) {}
3070 *
3071 * - `doc` The mongoose document which is being converted
3072 * - `ret` The plain object representation which has been converted
3073 * - `options` The options in use (either schema options or the options passed inline)
3074 *
3075 * ####Example
3076 *
3077 * // specify the transform schema option
3078 * if (!schema.options.toObject) schema.options.toObject = {};
3079 * schema.options.toObject.transform = function (doc, ret, options) {
3080 * // remove the _id of every document before returning the result
3081 * delete ret._id;
3082 * return ret;
3083 * }
3084 *
3085 * // without the transformation in the schema
3086 * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
3087 *
3088 * // with the transformation
3089 * doc.toObject(); // { name: 'Wreck-it Ralph' }
3090 *
3091 * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
3092 *
3093 * if (!schema.options.toObject) schema.options.toObject = {};
3094 * schema.options.toObject.transform = function (doc, ret, options) {
3095 * return { movie: ret.name }
3096 * }
3097 *
3098 * // without the transformation in the schema
3099 * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
3100 *
3101 * // with the transformation
3102 * doc.toObject(); // { movie: 'Wreck-it Ralph' }
3103 *
3104 * _Note: if a transform function returns `undefined`, the return value will be ignored._
3105 *
3106 * Transformations may also be applied inline, overridding any transform set in the options:
3107 *
3108 * function xform (doc, ret, options) {
3109 * return { inline: ret.name, custom: true }
3110 * }
3111 *
3112 * // pass the transform as an inline option
3113 * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
3114 *
3115 * If you want to skip transformations, use `transform: false`:
3116 *
3117 * schema.options.toObject.hide = '_id';
3118 * schema.options.toObject.transform = function (doc, ret, options) {
3119 * if (options.hide) {
3120 * options.hide.split(' ').forEach(function (prop) {
3121 * delete ret[prop];
3122 * });
3123 * }
3124 * return ret;
3125 * }
3126 *
3127 * var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
3128 * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
3129 * doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
3130 * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
3131 *
3132 * If you pass a transform in `toObject()` options, Mongoose will apply the transform
3133 * to [subdocuments](/docs/subdocs.html) in addition to the top-level document.
3134 * Similarly, `transform: false` skips transforms for all subdocuments.
3135 * Note that this is behavior is different for transforms defined in the schema:
3136 * if you define a transform in `schema.options.toObject.transform`, that transform
3137 * will **not** apply to subdocuments.
3138 *
3139 * const memberSchema = new Schema({ name: String, email: String });
3140 * const groupSchema = new Schema({ members: [memberSchema], name: String, email });
3141 * const Group = mongoose.model('Group', groupSchema);
3142 *
3143 * const doc = new Group({
3144 * name: 'Engineering',
3145 * email: 'dev@mongoosejs.io',
3146 * members: [{ name: 'Val', email: 'val@mongoosejs.io' }]
3147 * });
3148 *
3149 * // Removes `email` from both top-level document **and** array elements
3150 * // { name: 'Engineering', members: [{ name: 'Val' }] }
3151 * doc.toObject({ transform: (doc, ret) => { delete ret.email; return ret; } });
3152 *
3153 * Transforms, like all of these options, are also available for `toJSON`. See [this guide to `JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html) to learn why `toJSON()` and `toObject()` are separate functions.
3154 *
3155 * See [schema options](/docs/guide.html#toObject) for some more details.
3156 *
3157 * _During save, no custom options are applied to the document before being sent to the database._
3158 *
3159 * @param {Object} [options]
3160 * @param {Boolean} [options.getters=false] if true, apply all getters, including virtuals
3161 * @param {Boolean} [options.virtuals=false] if true, apply virtuals, including aliases. Use `{ getters: true, virtuals: false }` to just apply getters, not virtuals
3162 * @param {Boolean} [options.aliases=true] if `options.virtuals = true`, you can set `options.aliases = false` to skip applying aliases. This option is a no-op if `options.virtuals = false`.
3163 * @param {Boolean} [options.minimize=true] if true, omit any empty objects from the output
3164 * @param {Function|null} [options.transform=null] if set, mongoose will call this function to allow you to transform the returned object
3165 * @param {Boolean} [options.depopulate=false] if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths.
3166 * @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output
3167 * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
3168 * @return {Object} js object
3169 * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
3170 * @api public
3171 * @memberOf Document
3172 * @instance
3173 */
3174
3175Document.prototype.toObject = function(options) {
3176 return this.$toObject(options);
3177};
3178
3179/*!
3180 * Minimizes an object, removing undefined values and empty objects
3181 *
3182 * @param {Object} object to minimize
3183 * @return {Object}
3184 */
3185
3186function minimize(obj) {
3187 const keys = Object.keys(obj);
3188 let i = keys.length;
3189 let hasKeys;
3190 let key;
3191 let val;
3192
3193 while (i--) {
3194 key = keys[i];
3195 val = obj[key];
3196
3197 if (utils.isObject(val) && !Buffer.isBuffer(val)) {
3198 obj[key] = minimize(val);
3199 }
3200
3201 if (undefined === obj[key]) {
3202 delete obj[key];
3203 continue;
3204 }
3205
3206 hasKeys = true;
3207 }
3208
3209 return hasKeys
3210 ? obj
3211 : undefined;
3212}
3213
3214/*!
3215 * Applies virtuals properties to `json`.
3216 */
3217
3218function applyVirtuals(self, json, options, toObjectOptions) {
3219 const schema = self.schema;
3220 const paths = Object.keys(schema.virtuals);
3221 let i = paths.length;
3222 const numPaths = i;
3223 let path;
3224 let assignPath;
3225 let cur = self._doc;
3226 let v;
3227 const aliases = get(toObjectOptions, 'aliases', true);
3228
3229 if (!cur) {
3230 return json;
3231 }
3232
3233 options = options || {};
3234 for (i = 0; i < numPaths; ++i) {
3235 path = paths[i];
3236
3237 // Allow skipping aliases with `toObject({ virtuals: true, aliases: false })`
3238 if (!aliases && schema.aliases.hasOwnProperty(path)) {
3239 continue;
3240 }
3241
3242 // We may be applying virtuals to a nested object, for example if calling
3243 // `doc.nestedProp.toJSON()`. If so, the path we assign to, `assignPath`,
3244 // will be a trailing substring of the `path`.
3245 assignPath = path;
3246 if (options.path != null) {
3247 if (!path.startsWith(options.path + '.')) {
3248 continue;
3249 }
3250 assignPath = path.substr(options.path.length + 1);
3251 }
3252 const parts = assignPath.split('.');
3253 v = clone(self.get(path), options);
3254 if (v === void 0) {
3255 continue;
3256 }
3257 const plen = parts.length;
3258 cur = json;
3259 for (let j = 0; j < plen - 1; ++j) {
3260 cur[parts[j]] = cur[parts[j]] || {};
3261 cur = cur[parts[j]];
3262 }
3263 cur[parts[plen - 1]] = v;
3264 }
3265
3266 return json;
3267}
3268
3269/*!
3270 * Applies virtuals properties to `json`.
3271 *
3272 * @param {Document} self
3273 * @param {Object} json
3274 * @return {Object} `json`
3275 */
3276
3277function applyGetters(self, json, options) {
3278 const schema = self.schema;
3279 const paths = Object.keys(schema.paths);
3280 let i = paths.length;
3281 let path;
3282 let cur = self._doc;
3283 let v;
3284
3285 if (!cur) {
3286 return json;
3287 }
3288
3289 while (i--) {
3290 path = paths[i];
3291
3292 const parts = path.split('.');
3293 const plen = parts.length;
3294 const last = plen - 1;
3295 let branch = json;
3296 let part;
3297 cur = self._doc;
3298
3299 if (!self.isSelected(path)) {
3300 continue;
3301 }
3302
3303 for (let ii = 0; ii < plen; ++ii) {
3304 part = parts[ii];
3305 v = cur[part];
3306 if (ii === last) {
3307 const val = self.get(path);
3308 branch[part] = clone(val, options);
3309 } else if (v == null) {
3310 if (part in cur) {
3311 branch[part] = v;
3312 }
3313 break;
3314 } else {
3315 branch = branch[part] || (branch[part] = {});
3316 }
3317 cur = v;
3318 }
3319 }
3320
3321 return json;
3322}
3323
3324/*!
3325 * Applies schema type transforms to `json`.
3326 *
3327 * @param {Document} self
3328 * @param {Object} json
3329 * @return {Object} `json`
3330 */
3331
3332function applySchemaTypeTransforms(self, json) {
3333 const schema = self.schema;
3334 const paths = Object.keys(schema.paths || {});
3335 const cur = self._doc;
3336
3337 if (!cur) {
3338 return json;
3339 }
3340
3341 for (const path of paths) {
3342 const schematype = schema.paths[path];
3343 if (typeof schematype.options.transform === 'function') {
3344 const val = self.get(path);
3345 json[path] = schematype.options.transform.call(self, val);
3346 } else if (schematype.$embeddedSchemaType != null &&
3347 typeof schematype.$embeddedSchemaType.options.transform === 'function') {
3348 const vals = [].concat(self.get(path));
3349 const transform = schematype.$embeddedSchemaType.options.transform;
3350 for (let i = 0; i < vals.length; ++i) {
3351 vals[i] = transform.call(self, vals[i]);
3352 }
3353
3354 json[path] = vals;
3355 }
3356 }
3357
3358 return json;
3359}
3360
3361/**
3362 * The return value of this method is used in calls to JSON.stringify(doc).
3363 *
3364 * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument.
3365 *
3366 * schema.set('toJSON', { virtuals: true })
3367 *
3368 * See [schema options](/docs/guide.html#toJSON) for details.
3369 *
3370 * @param {Object} options
3371 * @return {Object}
3372 * @see Document#toObject #document_Document-toObject
3373 * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html
3374 * @api public
3375 * @memberOf Document
3376 * @instance
3377 */
3378
3379Document.prototype.toJSON = function(options) {
3380 return this.$toObject(options, true);
3381};
3382
3383/**
3384 * Helper for console.log
3385 *
3386 * @api public
3387 * @method inspect
3388 * @memberOf Document
3389 * @instance
3390 */
3391
3392Document.prototype.inspect = function(options) {
3393 const isPOJO = utils.isPOJO(options);
3394 let opts;
3395 if (isPOJO) {
3396 opts = options;
3397 opts.minimize = false;
3398 }
3399 const ret = this.toObject(opts);
3400
3401 if (ret == null) {
3402 // If `toObject()` returns null, `this` is still an object, so if `inspect()`
3403 // prints out null this can cause some serious confusion. See gh-7942.
3404 return 'MongooseDocument { ' + ret + ' }';
3405 }
3406
3407 return ret;
3408};
3409
3410if (inspect.custom) {
3411 /*!
3412 * Avoid Node deprecation warning DEP0079
3413 */
3414
3415 Document.prototype[inspect.custom] = Document.prototype.inspect;
3416}
3417
3418/**
3419 * Helper for console.log
3420 *
3421 * @api public
3422 * @method toString
3423 * @memberOf Document
3424 * @instance
3425 */
3426
3427Document.prototype.toString = function() {
3428 const ret = this.inspect();
3429 if (typeof ret === 'string') {
3430 return ret;
3431 }
3432 return inspect(ret);
3433};
3434
3435/**
3436 * Returns true if the Document stores the same data as doc.
3437 *
3438 * Documents are considered equal when they have matching `_id`s, unless neither
3439 * document has an `_id`, in which case this function falls back to using
3440 * `deepEqual()`.
3441 *
3442 * @param {Document} doc a document to compare
3443 * @return {Boolean}
3444 * @api public
3445 * @memberOf Document
3446 * @instance
3447 */
3448
3449Document.prototype.equals = function(doc) {
3450 if (!doc) {
3451 return false;
3452 }
3453
3454 const tid = this.get('_id');
3455 const docid = doc.get ? doc.get('_id') : doc;
3456 if (!tid && !docid) {
3457 return deepEqual(this, doc);
3458 }
3459 return tid && tid.equals
3460 ? tid.equals(docid)
3461 : tid === docid;
3462};
3463
3464/**
3465 * Populates document references, executing the `callback` when complete.
3466 * If you want to use promises instead, use this function with
3467 * [`execPopulate()`](#document_Document-execPopulate)
3468 *
3469 * ####Example:
3470 *
3471 * doc
3472 * .populate('company')
3473 * .populate({
3474 * path: 'notes',
3475 * match: /airline/,
3476 * select: 'text',
3477 * model: 'modelName'
3478 * options: opts
3479 * }, function (err, user) {
3480 * assert(doc._id === user._id) // the document itself is passed
3481 * })
3482 *
3483 * // summary
3484 * doc.populate(path) // not executed
3485 * doc.populate(options); // not executed
3486 * doc.populate(path, callback) // executed
3487 * doc.populate(options, callback); // executed
3488 * doc.populate(callback); // executed
3489 * doc.populate(options).execPopulate() // executed, returns promise
3490 *
3491 *
3492 * ####NOTE:
3493 *
3494 * Population does not occur unless a `callback` is passed *or* you explicitly
3495 * call `execPopulate()`.
3496 * Passing the same path a second time will overwrite the previous path options.
3497 * See [Model.populate()](#model_Model.populate) for explaination of options.
3498 *
3499 * @see Model.populate #model_Model.populate
3500 * @see Document.execPopulate #document_Document-execPopulate
3501 * @param {String|Object} [path] The path to populate or an options object
3502 * @param {Function} [callback] When passed, population is invoked
3503 * @api public
3504 * @return {Document} this
3505 * @memberOf Document
3506 * @instance
3507 */
3508
3509Document.prototype.populate = function populate() {
3510 if (arguments.length === 0) {
3511 return this;
3512 }
3513
3514 const pop = this.$__.populate || (this.$__.populate = {});
3515 const args = utils.args(arguments);
3516 let fn;
3517
3518 if (typeof args[args.length - 1] === 'function') {
3519 fn = args.pop();
3520 }
3521
3522 // allow `doc.populate(callback)`
3523 if (args.length) {
3524 // use hash to remove duplicate paths
3525 const res = utils.populate.apply(null, args);
3526 for (let i = 0; i < res.length; ++i) {
3527 pop[res[i].path] = res[i];
3528 }
3529 }
3530
3531 if (fn) {
3532 const paths = utils.object.vals(pop);
3533 this.$__.populate = undefined;
3534 let topLevelModel = this.constructor;
3535 if (this.$__isNested) {
3536 topLevelModel = this.$__.scope.constructor;
3537 const nestedPath = this.$__.nestedPath;
3538 paths.forEach(function(populateOptions) {
3539 populateOptions.path = nestedPath + '.' + populateOptions.path;
3540 });
3541 }
3542
3543 // Use `$session()` by default if the document has an associated session
3544 // See gh-6754
3545 if (this.$session() != null) {
3546 const session = this.$session();
3547 paths.forEach(path => {
3548 if (path.options == null) {
3549 path.options = { session: session };
3550 return;
3551 }
3552 if (!('session' in path.options)) {
3553 path.options.session = session;
3554 }
3555 });
3556 }
3557
3558 topLevelModel.populate(this, paths, fn);
3559 }
3560
3561 return this;
3562};
3563
3564/**
3565 * Explicitly executes population and returns a promise. Useful for ES2015
3566 * integration.
3567 *
3568 * ####Example:
3569 *
3570 * var promise = doc.
3571 * populate('company').
3572 * populate({
3573 * path: 'notes',
3574 * match: /airline/,
3575 * select: 'text',
3576 * model: 'modelName'
3577 * options: opts
3578 * }).
3579 * execPopulate();
3580 *
3581 * // summary
3582 * doc.execPopulate().then(resolve, reject);
3583 *
3584 *
3585 * @see Document.populate #document_Document-populate
3586 * @api public
3587 * @param {Function} [callback] optional callback. If specified, a promise will **not** be returned
3588 * @return {Promise} promise that resolves to the document when population is done
3589 * @memberOf Document
3590 * @instance
3591 */
3592
3593Document.prototype.execPopulate = function(callback) {
3594 return promiseOrCallback(callback, cb => {
3595 this.populate(cb);
3596 }, this.constructor.events);
3597};
3598
3599/**
3600 * Gets _id(s) used during population of the given `path`.
3601 *
3602 * ####Example:
3603 *
3604 * Model.findOne().populate('author').exec(function (err, doc) {
3605 * console.log(doc.author.name) // Dr.Seuss
3606 * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
3607 * })
3608 *
3609 * If the path was not populated, undefined is returned.
3610 *
3611 * @param {String} path
3612 * @return {Array|ObjectId|Number|Buffer|String|undefined}
3613 * @memberOf Document
3614 * @instance
3615 * @api public
3616 */
3617
3618Document.prototype.populated = function(path, val, options) {
3619 // val and options are internal
3620 if (val === null || val === void 0) {
3621 if (!this.$__.populated) {
3622 return undefined;
3623 }
3624 const v = this.$__.populated[path];
3625 if (v) {
3626 return v.value;
3627 }
3628 return undefined;
3629 }
3630
3631 // internal
3632 if (val === true) {
3633 if (!this.$__.populated) {
3634 return undefined;
3635 }
3636 return this.$__.populated[path];
3637 }
3638
3639 this.$__.populated || (this.$__.populated = {});
3640 this.$__.populated[path] = {value: val, options: options};
3641
3642 // If this was a nested populate, make sure each populated doc knows
3643 // about its populated children (gh-7685)
3644 const pieces = path.split('.');
3645 for (let i = 0; i < pieces.length - 1; ++i) {
3646 const subpath = pieces.slice(0, i + 1).join('.');
3647 const subdoc = this.get(subpath);
3648 if (subdoc != null && subdoc.$__ != null && this.populated(subpath)) {
3649 const rest = pieces.slice(i + 1).join('.');
3650 subdoc.populated(rest, val, options);
3651 // No need to continue because the above recursion should take care of
3652 // marking the rest of the docs as populated
3653 break;
3654 }
3655 }
3656
3657 return val;
3658};
3659
3660/**
3661 * Takes a populated field and returns it to its unpopulated state.
3662 *
3663 * ####Example:
3664 *
3665 * Model.findOne().populate('author').exec(function (err, doc) {
3666 * console.log(doc.author.name); // Dr.Seuss
3667 * console.log(doc.depopulate('author'));
3668 * console.log(doc.author); // '5144cf8050f071d979c118a7'
3669 * })
3670 *
3671 * If the path was not populated, this is a no-op.
3672 *
3673 * @param {String} path
3674 * @return {Document} this
3675 * @see Document.populate #document_Document-populate
3676 * @api public
3677 * @memberOf Document
3678 * @instance
3679 */
3680
3681Document.prototype.depopulate = function(path) {
3682 if (typeof path === 'string') {
3683 path = path.split(' ');
3684 }
3685 let populatedIds;
3686 const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
3687 const populated = get(this, '$__.populated', {});
3688
3689 if (arguments.length === 0) {
3690 // Depopulate all
3691 for (let i = 0; i < virtualKeys.length; i++) {
3692 delete this.$$populatedVirtuals[virtualKeys[i]];
3693 delete this._doc[virtualKeys[i]];
3694 delete populated[virtualKeys[i]];
3695 }
3696
3697 const keys = Object.keys(populated);
3698
3699 for (let i = 0; i < keys.length; i++) {
3700 populatedIds = this.populated(keys[i]);
3701 if (!populatedIds) {
3702 continue;
3703 }
3704 delete populated[keys[i]];
3705 this.$set(keys[i], populatedIds);
3706 }
3707 return this;
3708 }
3709
3710 for (let i = 0; i < path.length; i++) {
3711 populatedIds = this.populated(path[i]);
3712 delete populated[path[i]];
3713
3714 if (virtualKeys.indexOf(path[i]) !== -1) {
3715 delete this.$$populatedVirtuals[path[i]];
3716 delete this._doc[path[i]];
3717 } else if (populatedIds) {
3718 this.$set(path[i], populatedIds);
3719 }
3720 }
3721 return this;
3722};
3723
3724
3725/**
3726 * Returns the full path to this document.
3727 *
3728 * @param {String} [path]
3729 * @return {String}
3730 * @api private
3731 * @method $__fullPath
3732 * @memberOf Document
3733 * @instance
3734 */
3735
3736Document.prototype.$__fullPath = function(path) {
3737 // overridden in SubDocuments
3738 return path || '';
3739};
3740
3741/*!
3742 * Module exports.
3743 */
3744
3745Document.ValidationError = ValidationError;
3746module.exports = exports = Document;