UNPKG

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