summaryrefslogtreecommitdiffstats
path: root/node_modules/mongoose/lib/document.js
diff options
context:
space:
mode:
authorGravatar Piotr Russ <mail@pruss.it> 2020-11-16 00:10:28 +0100
committerGravatar Piotr Russ <mail@pruss.it> 2020-11-16 00:10:28 +0100
commite06ec920f7a5d784e674c4c4b4e6d1da3dc7391d (patch)
tree55713f725f77b44ebfec86e4eec3ce33e71458ca /node_modules/mongoose/lib/document.js
downloadwebsite_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.tar.gz
website_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.tar.bz2
website_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.zip
api, login, auth
Diffstat (limited to 'node_modules/mongoose/lib/document.js')
-rw-r--r--node_modules/mongoose/lib/document.js4056
1 files changed, 4056 insertions, 0 deletions
diff --git a/node_modules/mongoose/lib/document.js b/node_modules/mongoose/lib/document.js
new file mode 100644
index 0000000..9e8cb9a
--- /dev/null
+++ b/node_modules/mongoose/lib/document.js
@@ -0,0 +1,4056 @@
+'use strict';
+
+/*!
+ * Module dependencies.
+ */
+
+const EventEmitter = require('events').EventEmitter;
+const InternalCache = require('./internal');
+const MongooseError = require('./error/index');
+const MixedSchema = require('./schema/mixed');
+const ObjectExpectedError = require('./error/objectExpected');
+const ObjectParameterError = require('./error/objectParameter');
+const ParallelValidateError = require('./error/parallelValidate');
+const Schema = require('./schema');
+const StrictModeError = require('./error/strict');
+const ValidationError = require('./error/validation');
+const ValidatorError = require('./error/validator');
+const VirtualType = require('./virtualtype');
+const promiseOrCallback = require('./helpers/promiseOrCallback');
+const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
+const compile = require('./helpers/document/compile').compile;
+const defineKey = require('./helpers/document/compile').defineKey;
+const flatten = require('./helpers/common').flatten;
+const get = require('./helpers/get');
+const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
+const handleSpreadDoc = require('./helpers/document/handleSpreadDoc');
+const idGetter = require('./plugins/idGetter');
+const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
+const isExclusive = require('./helpers/projection/isExclusive');
+const inspect = require('util').inspect;
+const internalToObjectOptions = require('./options').internalToObjectOptions;
+const mpath = require('mpath');
+const queryhelpers = require('./queryhelpers');
+const utils = require('./utils');
+const isPromise = require('./helpers/isPromise');
+
+const clone = utils.clone;
+const deepEqual = utils.deepEqual;
+const isMongooseObject = utils.isMongooseObject;
+
+const arrayAtomicsBackupSymbol = Symbol('mongoose.Array#atomicsBackup');
+const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
+const documentArrayParent = require('./helpers/symbols').documentArrayParent;
+const documentIsSelected = require('./helpers/symbols').documentIsSelected;
+const documentIsModified = require('./helpers/symbols').documentIsModified;
+const documentModifiedPaths = require('./helpers/symbols').documentModifiedPaths;
+const documentSchemaSymbol = require('./helpers/symbols').documentSchemaSymbol;
+const getSymbol = require('./helpers/symbols').getSymbol;
+const populateModelSymbol = require('./helpers/symbols').populateModelSymbol;
+const scopeSymbol = require('./helpers/symbols').scopeSymbol;
+
+let DocumentArray;
+let MongooseArray;
+let Embedded;
+
+const specialProperties = utils.specialProperties;
+
+/**
+ * The core Mongoose document constructor. You should not call this directly,
+ * the Mongoose [Model constructor](./api.html#Model) calls this for you.
+ *
+ * @param {Object} obj the values to set
+ * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
+ * @param {Object} [options] various configuration options for the document
+ * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
+ * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
+ * @event `init`: Emitted on a document after it has been retrieved from the db and fully hydrated by Mongoose.
+ * @event `save`: Emitted when the document is successfully saved
+ * @api private
+ */
+
+function Document(obj, fields, skipId, options) {
+ if (typeof skipId === 'object' && skipId != null) {
+ options = skipId;
+ skipId = options.skipId;
+ }
+ options = Object.assign({}, options);
+ const defaults = get(options, 'defaults', true);
+ options.defaults = defaults;
+
+ // Support `browserDocument.js` syntax
+ if (this.schema == null) {
+ const _schema = utils.isObject(fields) && !fields.instanceOfSchema ?
+ new Schema(fields) :
+ fields;
+ this.$__setSchema(_schema);
+ fields = skipId;
+ skipId = options;
+ options = arguments[4] || {};
+ }
+
+ this.$__ = new InternalCache;
+ this.$__.emitter = new EventEmitter();
+ this.isNew = 'isNew' in options ? options.isNew : true;
+ this.errors = undefined;
+ this.$__.$options = options || {};
+ this.$locals = {};
+ this.$op = null;
+
+ if (obj != null && typeof obj !== 'object') {
+ throw new ObjectParameterError(obj, 'obj', 'Document');
+ }
+
+ const schema = this.schema;
+
+ if (typeof fields === 'boolean' || fields === 'throw') {
+ this.$__.strictMode = fields;
+ fields = undefined;
+ } else {
+ this.$__.strictMode = schema.options.strict;
+ this.$__.selected = fields;
+ }
+
+ const requiredPaths = schema.requiredPaths(true);
+ for (const path of requiredPaths) {
+ this.$__.activePaths.require(path);
+ }
+
+ this.$__.emitter.setMaxListeners(0);
+
+ let exclude = null;
+
+ // determine if this doc is a result of a query with
+ // excluded fields
+ if (utils.isPOJO(fields)) {
+ exclude = isExclusive(fields);
+ }
+
+ const hasIncludedChildren = exclude === false && fields ?
+ $__hasIncludedChildren(fields) :
+ {};
+
+ if (this._doc == null) {
+ this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
+
+ // By default, defaults get applied **before** setting initial values
+ // Re: gh-6155
+ if (defaults) {
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
+ isNew: this.isNew
+ });
+ }
+ }
+
+ if (obj) {
+ // Skip set hooks
+ if (this.$__original_set) {
+ this.$__original_set(obj, undefined, true);
+ } else {
+ this.$set(obj, undefined, true);
+ }
+
+ if (obj instanceof Document) {
+ this.isNew = obj.isNew;
+ }
+ }
+
+ // Function defaults get applied **after** setting initial values so they
+ // see the full doc rather than an empty one, unless they opt out.
+ // Re: gh-3781, gh-6155
+ if (options.willInit && defaults) {
+ EventEmitter.prototype.once.call(this, 'init', () => {
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
+ isNew: this.isNew
+ });
+ });
+ } else if (defaults) {
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
+ isNew: this.isNew
+ });
+ }
+
+ this.$__._id = this._id;
+
+ if (!this.$__.strictMode && obj) {
+ const _this = this;
+ const keys = Object.keys(this._doc);
+
+ keys.forEach(function(key) {
+ if (!(key in schema.tree)) {
+ defineKey(key, null, _this);
+ }
+ });
+ }
+
+ applyQueue(this);
+}
+
+/*!
+ * Document exposes the NodeJS event emitter API, so you can use
+ * `on`, `once`, etc.
+ */
+utils.each(
+ ['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
+ 'removeAllListeners', 'addListener'],
+ function(emitterFn) {
+ Document.prototype[emitterFn] = function() {
+ return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments);
+ };
+ });
+
+Document.prototype.constructor = Document;
+
+for (const i in EventEmitter.prototype) {
+ Document[i] = EventEmitter.prototype[i];
+}
+
+/**
+ * The documents schema.
+ *
+ * @api public
+ * @property schema
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.schema;
+
+/**
+ * Empty object that you can use for storing properties on the document. This
+ * is handy for passing data to middleware without conflicting with Mongoose
+ * internals.
+ *
+ * ####Example:
+ *
+ * schema.pre('save', function() {
+ * // Mongoose will set `isNew` to `false` if `save()` succeeds
+ * this.$locals.wasNew = this.isNew;
+ * });
+ *
+ * schema.post('save', function() {
+ * // Prints true if `isNew` was set before `save()`
+ * console.log(this.$locals.wasNew);
+ * });
+ *
+ * @api public
+ * @property $locals
+ * @memberOf Document
+ * @instance
+ */
+
+Object.defineProperty(Document.prototype, '$locals', {
+ configurable: false,
+ enumerable: false,
+ writable: true
+});
+
+/**
+ * Boolean flag specifying if the document is new.
+ *
+ * @api public
+ * @property isNew
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.isNew;
+
+/**
+ * The string version of this documents _id.
+ *
+ * ####Note:
+ *
+ * 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.
+ *
+ * new Schema({ name: String }, { id: false });
+ *
+ * @api public
+ * @see Schema options /docs/guide.html#options
+ * @property id
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.id;
+
+/**
+ * Hash containing current validation errors.
+ *
+ * @api public
+ * @property errors
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.errors;
+
+/**
+ * A string containing the current operation that Mongoose is executing
+ * on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`.
+ *
+ * ####Example:
+ *
+ * const doc = new Model({ name: 'test' });
+ * doc.$op; // null
+ *
+ * const promise = doc.save();
+ * doc.$op; // 'save'
+ *
+ * await promise;
+ * doc.$op; // null
+ *
+ * @api public
+ * @property $op
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$op;
+
+/*!
+ * ignore
+ */
+
+function $__hasIncludedChildren(fields) {
+ const hasIncludedChildren = {};
+ const keys = Object.keys(fields);
+
+ for (const key of keys) {
+ const parts = key.split('.');
+ const c = [];
+
+ for (const part of parts) {
+ c.push(part);
+ hasIncludedChildren[c.join('.')] = 1;
+ }
+ }
+
+ return hasIncludedChildren;
+}
+
+/*!
+ * ignore
+ */
+
+function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
+ const paths = Object.keys(doc.schema.paths);
+ const plen = paths.length;
+
+ for (let i = 0; i < plen; ++i) {
+ let def;
+ let curPath = '';
+ const p = paths[i];
+
+ if (p === '_id' && skipId) {
+ continue;
+ }
+
+ const type = doc.schema.paths[p];
+ const path = p.indexOf('.') === -1 ? [p] : p.split('.');
+ const len = path.length;
+ let included = false;
+ let doc_ = doc._doc;
+
+ for (let j = 0; j < len; ++j) {
+ if (doc_ == null) {
+ break;
+ }
+
+ const piece = path[j];
+ curPath += (!curPath.length ? '' : '.') + piece;
+
+ if (exclude === true) {
+ if (curPath in fields) {
+ break;
+ }
+ } else if (exclude === false && fields && !included) {
+ if (curPath in fields) {
+ included = true;
+ } else if (!hasIncludedChildren[curPath]) {
+ break;
+ }
+ }
+
+ if (j === len - 1) {
+ if (doc_[piece] !== void 0) {
+ break;
+ }
+
+ if (typeof type.defaultValue === 'function') {
+ if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
+ break;
+ }
+ if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
+ break;
+ }
+ } else if (!isBeforeSetters) {
+ // Non-function defaults should always run **before** setters
+ continue;
+ }
+
+ if (pathsToSkip && pathsToSkip[curPath]) {
+ break;
+ }
+
+ if (fields && exclude !== null) {
+ if (exclude === true) {
+ // apply defaults to all non-excluded fields
+ if (p in fields) {
+ continue;
+ }
+
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ doc.$__.activePaths.default(p);
+ }
+ } else if (included) {
+ // selected field
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ doc.$__.activePaths.default(p);
+ }
+ }
+ } else {
+ try {
+ def = type.getDefault(doc, false);
+ } catch (err) {
+ doc.invalidate(p, err);
+ break;
+ }
+
+ if (typeof def !== 'undefined') {
+ doc_[piece] = def;
+ doc.$__.activePaths.default(p);
+ }
+ }
+ } else {
+ doc_ = doc_[piece];
+ }
+ }
+ }
+}
+
+/**
+ * Builds the default doc structure
+ *
+ * @param {Object} obj
+ * @param {Object} [fields]
+ * @param {Boolean} [skipId]
+ * @api private
+ * @method $__buildDoc
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) {
+ const doc = {};
+
+ const paths = Object.keys(this.schema.paths).
+ // Don't build up any paths that are underneath a map, we don't know
+ // what the keys will be
+ filter(p => !p.includes('$*'));
+ const plen = paths.length;
+ let ii = 0;
+
+ for (; ii < plen; ++ii) {
+ const p = paths[ii];
+
+ if (p === '_id') {
+ if (skipId) {
+ continue;
+ }
+ if (obj && '_id' in obj) {
+ continue;
+ }
+ }
+
+ const path = p.split('.');
+ const len = path.length;
+ const last = len - 1;
+ let curPath = '';
+ let doc_ = doc;
+ let included = false;
+
+ for (let i = 0; i < len; ++i) {
+ const piece = path[i];
+
+ curPath += (!curPath.length ? '' : '.') + piece;
+
+ // support excluding intermediary levels
+ if (exclude === true) {
+ if (curPath in fields) {
+ break;
+ }
+ } else if (exclude === false && fields && !included) {
+ if (curPath in fields) {
+ included = true;
+ } else if (!hasIncludedChildren[curPath]) {
+ break;
+ }
+ }
+
+ if (i < last) {
+ doc_ = doc_[piece] || (doc_[piece] = {});
+ }
+ }
+ }
+
+ this._doc = doc;
+};
+
+/*!
+ * Converts to POJO when you use the document for querying
+ */
+
+Document.prototype.toBSON = function() {
+ return this.toObject(internalToObjectOptions);
+};
+
+/**
+ * Initializes the document without setters or marking anything modified.
+ *
+ * Called internally after a document is returned from mongodb. Normally,
+ * you do **not** need to call this function on your own.
+ *
+ * This function triggers `init` [middleware](/docs/middleware.html).
+ * Note that `init` hooks are [synchronous](/docs/middleware.html#synchronous).
+ *
+ * @param {Object} doc document returned by mongo
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.init = function(doc, opts, fn) {
+ if (typeof opts === 'function') {
+ fn = opts;
+ opts = null;
+ }
+
+ this.$__init(doc, opts);
+
+ if (fn) {
+ fn(null, this);
+ }
+
+ return this;
+};
+
+/*!
+ * ignore
+ */
+
+Document.prototype.$__init = function(doc, opts) {
+ this.isNew = false;
+ this.$init = true;
+ opts = opts || {};
+
+ // handle docs with populated paths
+ // If doc._id is not null or undefined
+ if (doc._id != null && opts.populated && opts.populated.length) {
+ const id = String(doc._id);
+ for (const item of opts.populated) {
+ if (item.isVirtual) {
+ this.populated(item.path, utils.getValue(item.path, doc), item);
+ } else {
+ this.populated(item.path, item._docs[id], item);
+ }
+
+ if (item._childDocs == null) {
+ continue;
+ }
+ for (const child of item._childDocs) {
+ if (child == null || child.$__ == null) {
+ continue;
+ }
+ child.$__.parent = this;
+ }
+ }
+ }
+
+ init(this, doc, this._doc, opts);
+
+ markArraySubdocsPopulated(this, opts.populated);
+
+ this.emit('init', this);
+ this.constructor.emit('init', this);
+
+ this.$__._id = this._id;
+
+ return this;
+};
+
+/*!
+ * If populating a path within a document array, make sure each
+ * subdoc within the array knows its subpaths are populated.
+ *
+ * ####Example:
+ * const doc = await Article.findOne().populate('comments.author');
+ * doc.comments[0].populated('author'); // Should be set
+ */
+
+function markArraySubdocsPopulated(doc, populated) {
+ if (doc._id == null || populated == null || populated.length === 0) {
+ return;
+ }
+
+ const id = String(doc._id);
+ for (const item of populated) {
+ if (item.isVirtual) {
+ continue;
+ }
+ const path = item.path;
+ const pieces = path.split('.');
+ for (let i = 0; i < pieces.length - 1; ++i) {
+ const subpath = pieces.slice(0, i + 1).join('.');
+ const rest = pieces.slice(i + 1).join('.');
+ const val = doc.get(subpath);
+ if (val == null) {
+ continue;
+ }
+
+ if (val.isMongooseDocumentArray) {
+ for (let j = 0; j < val.length; ++j) {
+ val[j].populated(rest, item._docs[id] == null ? [] : item._docs[id][j], item);
+ }
+ break;
+ }
+ }
+ }
+}
+
+/*!
+ * Init helper.
+ *
+ * @param {Object} self document instance
+ * @param {Object} obj raw mongodb doc
+ * @param {Object} doc object we are initializing
+ * @api private
+ */
+
+function init(self, obj, doc, opts, prefix) {
+ prefix = prefix || '';
+
+ const keys = Object.keys(obj);
+ const len = keys.length;
+ let schema;
+ let path;
+ let i;
+ let index = 0;
+
+ while (index < len) {
+ _init(index++);
+ }
+
+ function _init(index) {
+ i = keys[index];
+ path = prefix + i;
+ schema = self.schema.path(path);
+
+ // Should still work if not a model-level discriminator, but should not be
+ // necessary. This is *only* to catch the case where we queried using the
+ // base model and the discriminated model has a projection
+ if (self.schema.$isRootDiscriminator && !self.isSelected(path)) {
+ return;
+ }
+
+ if (!schema && utils.isPOJO(obj[i])) {
+ // assume nested object
+ if (!doc[i]) {
+ doc[i] = {};
+ }
+ init(self, obj[i], doc[i], opts, path + '.');
+ } else if (!schema) {
+ doc[i] = obj[i];
+ } else {
+ if (obj[i] === null) {
+ doc[i] = schema._castNullish(null);
+ } else if (obj[i] !== undefined) {
+ const intCache = obj[i].$__ || {};
+ const wasPopulated = intCache.wasPopulated || null;
+
+ if (schema && !wasPopulated) {
+ try {
+ doc[i] = schema.cast(obj[i], self, true);
+ } catch (e) {
+ self.invalidate(e.path, new ValidatorError({
+ path: e.path,
+ message: e.message,
+ type: 'cast',
+ value: e.value,
+ reason: e
+ }));
+ }
+ } else {
+ doc[i] = obj[i];
+ }
+ }
+ // mark as hydrated
+ if (!self.isModified(path)) {
+ self.$__.activePaths.init(path);
+ }
+ }
+ }
+}
+
+/**
+ * Sends an update command with this document `_id` as the query selector.
+ *
+ * ####Example:
+ *
+ * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
+ *
+ * ####Valid options:
+ *
+ * - same as in [Model.update](#model_Model.update)
+ *
+ * @see Model.update #model_Model.update
+ * @param {Object} doc
+ * @param {Object} options
+ * @param {Function} callback
+ * @return {Query}
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.update = function update() {
+ const args = utils.args(arguments);
+ args.unshift({ _id: this._id });
+ const query = this.constructor.update.apply(this.constructor, args);
+
+ if (this.$session() != null) {
+ if (!('session' in query.options)) {
+ query.options.session = this.$session();
+ }
+ }
+
+ return query;
+};
+
+/**
+ * Sends an updateOne command with this document `_id` as the query selector.
+ *
+ * ####Example:
+ *
+ * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
+ *
+ * ####Valid options:
+ *
+ * - same as in [Model.updateOne](#model_Model.updateOne)
+ *
+ * @see Model.updateOne #model_Model.updateOne
+ * @param {Object} doc
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @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).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
+ * @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.
+ * @param {Function} callback
+ * @return {Query}
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.updateOne = function updateOne(doc, options, callback) {
+ const query = this.constructor.updateOne({ _id: this._id }, doc, options);
+ query._pre(cb => {
+ this.constructor._middleware.execPre('updateOne', this, [this], cb);
+ });
+ query._post(cb => {
+ this.constructor._middleware.execPost('updateOne', this, [this], {}, cb);
+ });
+
+ if (this.$session() != null) {
+ if (!('session' in query.options)) {
+ query.options.session = this.$session();
+ }
+ }
+
+ if (callback != null) {
+ return query.exec(callback);
+ }
+
+ return query;
+};
+
+/**
+ * Sends a replaceOne command with this document `_id` as the query selector.
+ *
+ * ####Valid options:
+ *
+ * - same as in [Model.replaceOne](#model_Model.replaceOne)
+ *
+ * @see Model.replaceOne #model_Model.replaceOne
+ * @param {Object} doc
+ * @param {Object} options
+ * @param {Function} callback
+ * @return {Query}
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.replaceOne = function replaceOne() {
+ const args = utils.args(arguments);
+ args.unshift({ _id: this._id });
+ return this.constructor.replaceOne.apply(this.constructor, args);
+};
+
+/**
+ * Getter/setter around the session associated with this document. Used to
+ * automatically set `session` if you `save()` a doc that you got from a
+ * query with an associated session.
+ *
+ * ####Example:
+ *
+ * const session = MyModel.startSession();
+ * const doc = await MyModel.findOne().session(session);
+ * doc.$session() === session; // true
+ * doc.$session(null);
+ * doc.$session() === null; // true
+ *
+ * If this is a top-level document, setting the session propagates to all child
+ * docs.
+ *
+ * @param {ClientSession} [session] overwrite the current session
+ * @return {ClientSession}
+ * @method $session
+ * @api public
+ * @memberOf Document
+ */
+
+Document.prototype.$session = function $session(session) {
+ if (arguments.length === 0) {
+ return this.$__.session;
+ }
+ this.$__.session = session;
+
+ if (!this.ownerDocument) {
+ const subdocs = this.$__getAllSubdocs();
+ for (const child of subdocs) {
+ child.$session(session);
+ }
+ }
+
+ return session;
+};
+
+/**
+ * Overwrite all values in this document with the values of `obj`, except
+ * for immutable properties. Behaves similarly to `set()`, except for it
+ * unsets all properties that aren't in `obj`.
+ *
+ * @param {Object} obj the object to overwrite this document with
+ * @method overwrite
+ * @name overwrite
+ * @memberOf Document
+ * @instance
+ * @api public
+ */
+
+Document.prototype.overwrite = function overwrite(obj) {
+ const keys = Array.from(new Set(Object.keys(this._doc).concat(Object.keys(obj))));
+
+ for (const key of keys) {
+ if (key === '_id') {
+ continue;
+ }
+ // Explicitly skip version key
+ if (this.schema.options.versionKey && key === this.schema.options.versionKey) {
+ continue;
+ }
+ if (this.schema.options.discriminatorKey && key === this.schema.options.discriminatorKey) {
+ continue;
+ }
+ this.$set(key, obj[key]);
+ }
+
+ return this;
+};
+
+/**
+ * Alias for `set()`, used internally to avoid conflicts
+ *
+ * @param {String|Object} path path or object of key/vals to set
+ * @param {Any} val the value to set
+ * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
+ * @param {Object} [options] optionally specify options that modify the behavior of the set
+ * @method $set
+ * @name $set
+ * @memberOf Document
+ * @instance
+ * @api public
+ */
+
+Document.prototype.$set = function $set(path, val, type, options) {
+ if (utils.isPOJO(type)) {
+ options = type;
+ type = undefined;
+ }
+
+ options = options || {};
+ const merge = options.merge;
+ const adhoc = type && type !== true;
+ const constructing = type === true;
+ let adhocs;
+ let keys;
+ let i = 0;
+ let pathtype;
+ let key;
+ let prefix;
+
+ const strict = 'strict' in options
+ ? options.strict
+ : this.$__.strictMode;
+
+ if (adhoc) {
+ adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
+ adhocs[path] = this.schema.interpretAsType(path, type, this.schema.options);
+ }
+
+ if (typeof path !== 'string') {
+ // new Document({ key: val })
+ if (path instanceof Document) {
+ if (path.$__isNested) {
+ path = path.toObject();
+ } else {
+ path = path._doc;
+ }
+ }
+
+ if (path == null) {
+ const _ = path;
+ path = val;
+ val = _;
+ } else {
+ prefix = val ? val + '.' : '';
+
+ keys = Object.keys(path);
+ const len = keys.length;
+
+ // `_skipMinimizeTopLevel` is because we may have deleted the top-level
+ // nested key to ensure key order.
+ const _skipMinimizeTopLevel = get(options, '_skipMinimizeTopLevel', false);
+ if (len === 0 && _skipMinimizeTopLevel) {
+ delete options._skipMinimizeTopLevel;
+ if (val) {
+ this.$set(val, {});
+ }
+ return this;
+ }
+
+ while (i < len) {
+ _handleIndex.call(this, i++);
+ }
+
+ return this;
+ }
+ } else {
+ this.$__.$setCalled.add(path);
+ }
+
+ function _handleIndex(i) {
+ key = keys[i];
+ const pathName = prefix + key;
+ pathtype = this.schema.pathType(pathName);
+
+ // On initial set, delete any nested keys if we're going to overwrite
+ // them to ensure we keep the user's key order.
+ if (type === true &&
+ !prefix &&
+ path[key] != null &&
+ pathtype === 'nested' &&
+ this._doc[key] != null &&
+ Object.keys(this._doc[key]).length === 0) {
+ delete this._doc[key];
+ // Make sure we set `{}` back even if we minimize re: gh-8565
+ options = Object.assign({}, options, { _skipMinimizeTopLevel: true });
+ }
+
+ if (typeof path[key] === 'object' &&
+ !utils.isNativeObject(path[key]) &&
+ !utils.isMongooseType(path[key]) &&
+ path[key] != null &&
+ pathtype !== 'virtual' &&
+ pathtype !== 'real' &&
+ pathtype !== 'adhocOrUndefined' &&
+ !(this.$__path(pathName) instanceof MixedSchema) &&
+ !(this.schema.paths[pathName] &&
+ this.schema.paths[pathName].options &&
+ this.schema.paths[pathName].options.ref)) {
+ this.$__.$setCalled.add(prefix + key);
+ this.$set(path[key], prefix + key, constructing, options);
+ } else if (strict) {
+ // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039)
+ if (constructing && path[key] === void 0 &&
+ this.get(pathName) !== void 0) {
+ return;
+ }
+
+ if (pathtype === 'adhocOrUndefined') {
+ pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true });
+ }
+
+ if (pathtype === 'real' || pathtype === 'virtual') {
+ // Check for setting single embedded schema to document (gh-3535)
+ let p = path[key];
+ if (this.schema.paths[pathName] &&
+ this.schema.paths[pathName].$isSingleNested &&
+ path[key] instanceof Document) {
+ p = p.toObject({ virtuals: false, transform: false });
+ }
+ this.$set(prefix + key, p, constructing, options);
+ } else if (pathtype === 'nested' && path[key] instanceof Document) {
+ this.$set(prefix + key,
+ path[key].toObject({ transform: false }), constructing, options);
+ } else if (strict === 'throw') {
+ if (pathtype === 'nested') {
+ throw new ObjectExpectedError(key, path[key]);
+ } else {
+ throw new StrictModeError(key);
+ }
+ }
+ } else if (path[key] !== void 0) {
+ this.$set(prefix + key, path[key], constructing, options);
+ }
+ }
+
+ let pathType = this.schema.pathType(path);
+ if (pathType === 'adhocOrUndefined') {
+ pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true });
+ }
+
+ // Assume this is a Mongoose document that was copied into a POJO using
+ // `Object.assign()` or `{...doc}`
+ val = handleSpreadDoc(val);
+
+ if (pathType === 'nested' && val) {
+ if (typeof val === 'object' && val != null) {
+ if (!merge) {
+ this.$__setValue(path, null);
+ cleanModifiedSubpaths(this, path);
+ } else {
+ return this.$set(val, path, constructing);
+ }
+
+ const keys = Object.keys(val);
+ this.$__setValue(path, {});
+ for (const key of keys) {
+ this.$set(path + '.' + key, val[key], constructing);
+ }
+ this.markModified(path);
+ cleanModifiedSubpaths(this, path, { skipDocArrays: true });
+ return this;
+ }
+ this.invalidate(path, new MongooseError.CastError('Object', val, path));
+ return this;
+ }
+
+ let schema;
+ const parts = path.indexOf('.') === -1 ? [path] : path.split('.');
+
+ // Might need to change path for top-level alias
+ if (typeof this.schema.aliases[parts[0]] == 'string') {
+ parts[0] = this.schema.aliases[parts[0]];
+ }
+
+ if (pathType === 'adhocOrUndefined' && strict) {
+ // check for roots that are Mixed types
+ let mixed;
+
+ for (i = 0; i < parts.length; ++i) {
+ const subpath = parts.slice(0, i + 1).join('.');
+
+ // If path is underneath a virtual, bypass everything and just set it.
+ if (i + 1 < parts.length && this.schema.pathType(subpath) === 'virtual') {
+ mpath.set(path, val, this);
+ return this;
+ }
+
+ schema = this.schema.path(subpath);
+ if (schema == null) {
+ continue;
+ }
+
+ if (schema instanceof MixedSchema) {
+ // allow changes to sub paths of mixed types
+ mixed = true;
+ break;
+ }
+ }
+
+ if (schema == null) {
+ // Check for embedded discriminators
+ schema = getEmbeddedDiscriminatorPath(this, path);
+ }
+
+ if (!mixed && !schema) {
+ if (strict === 'throw') {
+ throw new StrictModeError(path);
+ }
+ return this;
+ }
+ } else if (pathType === 'virtual') {
+ schema = this.schema.virtualpath(path);
+ schema.applySetters(val, this);
+ return this;
+ } else {
+ schema = this.$__path(path);
+ }
+
+ // gh-4578, if setting a deeply nested path that doesn't exist yet, create it
+ let cur = this._doc;
+ let curPath = '';
+ for (i = 0; i < parts.length - 1; ++i) {
+ cur = cur[parts[i]];
+ curPath += (curPath.length > 0 ? '.' : '') + parts[i];
+ if (!cur) {
+ this.$set(curPath, {});
+ // Hack re: gh-5800. If nested field is not selected, it probably exists
+ // so `MongoError: cannot use the part (nested of nested.num) to
+ // traverse the element ({nested: null})` is not likely. If user gets
+ // that error, its their fault for now. We should reconsider disallowing
+ // modifying not selected paths for 6.x
+ if (!this.isSelected(curPath)) {
+ this.unmarkModified(curPath);
+ }
+ cur = this.$__getValue(curPath);
+ }
+ }
+
+ let pathToMark;
+
+ // When using the $set operator the path to the field must already exist.
+ // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
+
+ if (parts.length <= 1) {
+ pathToMark = path;
+ } else {
+ for (i = 0; i < parts.length; ++i) {
+ const subpath = parts.slice(0, i + 1).join('.');
+ if (this.get(subpath, null, { getters: false }) === null) {
+ pathToMark = subpath;
+ break;
+ }
+ }
+
+ if (!pathToMark) {
+ pathToMark = path;
+ }
+ }
+
+ // if this doc is being constructed we should not trigger getters
+ const priorVal = (() => {
+ if (this.$__.$options.priorDoc != null) {
+ return this.$__.$options.priorDoc.$__getValue(path);
+ }
+ if (constructing) {
+ return void 0;
+ }
+ return this.$__getValue(path);
+ })();
+
+ if (!schema) {
+ this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
+ return this;
+ }
+
+ // If overwriting a subdocument path, make sure to clear out
+ // any errors _before_ setting, so new errors that happen
+ // get persisted. Re: #9080
+ if (schema.$isSingleNested || schema.$isMongooseArray) {
+ _markValidSubpaths(this, path);
+ }
+
+ if (schema.$isSingleNested && val != null && merge) {
+ if (val instanceof Document) {
+ val = val.toObject({ virtuals: false, transform: false });
+ }
+ const keys = Object.keys(val);
+ for (const key of keys) {
+ this.$set(path + '.' + key, val[key], constructing, options);
+ }
+
+ return this;
+ }
+
+ let shouldSet = true;
+ try {
+ // If the user is trying to set a ref path to a document with
+ // the correct model name, treat it as populated
+ const refMatches = (() => {
+ if (schema.options == null) {
+ return false;
+ }
+ if (!(val instanceof Document)) {
+ return false;
+ }
+ const model = val.constructor;
+
+ // Check ref
+ const ref = schema.options.ref;
+ if (ref != null && (ref === model.modelName || ref === model.baseModelName)) {
+ return true;
+ }
+
+ // Check refPath
+ const refPath = schema.options.refPath;
+ if (refPath == null) {
+ return false;
+ }
+ const modelName = val.get(refPath);
+ return modelName === model.modelName || modelName === model.baseModelName;
+ })();
+
+ let didPopulate = false;
+ if (refMatches && val instanceof Document) {
+ this.populated(path, val._id, { [populateModelSymbol]: val.constructor });
+ didPopulate = true;
+ }
+
+ let popOpts;
+ if (schema.options &&
+ Array.isArray(schema.options[this.schema.options.typeKey]) &&
+ schema.options[this.schema.options.typeKey].length &&
+ schema.options[this.schema.options.typeKey][0].ref &&
+ _isManuallyPopulatedArray(val, schema.options[this.schema.options.typeKey][0].ref)) {
+ if (this.ownerDocument) {
+ popOpts = { [populateModelSymbol]: val[0].constructor };
+ this.ownerDocument().populated(this.$__fullPath(path),
+ val.map(function(v) { return v._id; }), popOpts);
+ } else {
+ popOpts = { [populateModelSymbol]: val[0].constructor };
+ this.populated(path, val.map(function(v) { return v._id; }), popOpts);
+ }
+ didPopulate = true;
+ }
+
+ if (this.schema.singleNestedPaths[path] == null) {
+ // If this path is underneath a single nested schema, we'll call the setter
+ // later in `$__set()` because we don't take `_doc` when we iterate through
+ // a single nested doc. That's to make sure we get the correct context.
+ // Otherwise we would double-call the setter, see gh-7196.
+ val = schema.applySetters(val, this, false, priorVal);
+ }
+
+ if (schema.$isMongooseDocumentArray &&
+ Array.isArray(val) &&
+ val.length > 0 &&
+ val[0] != null &&
+ val[0].$__ != null &&
+ val[0].$__.populated != null) {
+ const populatedPaths = Object.keys(val[0].$__.populated);
+ for (const populatedPath of populatedPaths) {
+ this.populated(path + '.' + populatedPath,
+ val.map(v => v.populated(populatedPath)),
+ val[0].$__.populated[populatedPath].options);
+ }
+ didPopulate = true;
+ }
+
+ if (!didPopulate && this.$__.populated) {
+ // If this array partially contains populated documents, convert them
+ // all to ObjectIds re: #8443
+ if (Array.isArray(val) && this.$__.populated[path]) {
+ for (let i = 0; i < val.length; ++i) {
+ if (val[i] instanceof Document) {
+ val[i] = val[i]._id;
+ }
+ }
+ }
+ delete this.$__.populated[path];
+ }
+
+ if (schema.$isSingleNested && val != null) {
+ _checkImmutableSubpaths(val, schema, priorVal);
+ }
+
+ this.$markValid(path);
+ } catch (e) {
+ if (e instanceof MongooseError.StrictModeError && e.isImmutableError) {
+ this.invalidate(path, e);
+ } else if (e instanceof MongooseError.CastError) {
+ this.invalidate(e.path, e);
+ if (e.$originalErrorPath) {
+ this.invalidate(path,
+ new MongooseError.CastError(schema.instance, val, path, e.$originalErrorPath));
+ }
+ } else {
+ this.invalidate(path,
+ new MongooseError.CastError(schema.instance, val, path, e));
+ }
+ shouldSet = false;
+ }
+
+ if (shouldSet) {
+ this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
+ }
+
+ if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) {
+ cleanModifiedSubpaths(this, path);
+ }
+
+ return this;
+};
+
+/*!
+ * ignore
+ */
+
+function _isManuallyPopulatedArray(val, ref) {
+ if (!Array.isArray(val)) {
+ return false;
+ }
+ if (val.length === 0) {
+ return false;
+ }
+
+ for (const el of val) {
+ if (!(el instanceof Document)) {
+ return false;
+ }
+ const modelName = el.constructor.modelName;
+ if (modelName == null) {
+ return false;
+ }
+ if (el.constructor.modelName != ref && el.constructor.baseModelName != ref) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Sets the value of a path, or many paths.
+ *
+ * ####Example:
+ *
+ * // path, value
+ * doc.set(path, value)
+ *
+ * // object
+ * doc.set({
+ * path : value
+ * , path2 : {
+ * path : value
+ * }
+ * })
+ *
+ * // on-the-fly cast to number
+ * doc.set(path, value, Number)
+ *
+ * // on-the-fly cast to string
+ * doc.set(path, value, String)
+ *
+ * // changing strict mode behavior
+ * doc.set(path, value, { strict: false });
+ *
+ * @param {String|Object} path path or object of key/vals to set
+ * @param {Any} val the value to set
+ * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
+ * @param {Object} [options] optionally specify options that modify the behavior of the set
+ * @api public
+ * @method set
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.set = Document.prototype.$set;
+
+/**
+ * Determine if we should mark this change as modified.
+ *
+ * @return {Boolean}
+ * @api private
+ * @method $__shouldModify
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__shouldModify = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
+ if (this.isNew) {
+ return true;
+ }
+
+ // Re: the note about gh-7196, `val` is the raw value without casting or
+ // setters if the full path is under a single nested subdoc because we don't
+ // want to double run setters. So don't set it as modified. See gh-7264.
+ if (this.schema.singleNestedPaths[path] != null) {
+ return false;
+ }
+
+ if (val === void 0 && !this.isSelected(path)) {
+ // when a path is not selected in a query, its initial
+ // value will be undefined.
+ return true;
+ }
+
+ if (val === void 0 && path in this.$__.activePaths.states.default) {
+ // we're just unsetting the default value which was never saved
+ return false;
+ }
+
+ // gh-3992: if setting a populated field to a doc, don't mark modified
+ // if they have the same _id
+ if (this.populated(path) &&
+ val instanceof Document &&
+ deepEqual(val._id, priorVal)) {
+ return false;
+ }
+
+ if (!deepEqual(val, priorVal || utils.getValue(path, this))) {
+ return true;
+ }
+
+ if (!constructing &&
+ val !== null &&
+ val !== undefined &&
+ path in this.$__.activePaths.states.default &&
+ deepEqual(val, schema.getDefault(this, constructing))) {
+ // a path with a default was $unset on the server
+ // and the user is setting it to the same value again
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Handles the actual setting of the value and marking the path modified if appropriate.
+ *
+ * @api private
+ * @method $__set
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__set = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
+ Embedded = Embedded || require('./types/embedded');
+
+ const shouldModify = this.$__shouldModify(pathToMark, path, constructing, parts,
+ schema, val, priorVal);
+ const _this = this;
+
+ if (shouldModify) {
+ this.markModified(pathToMark);
+
+ // handle directly setting arrays (gh-1126)
+ MongooseArray || (MongooseArray = require('./types/array'));
+ if (val && val.isMongooseArray) {
+ val._registerAtomic('$set', val);
+
+ // Update embedded document parent references (gh-5189)
+ if (val.isMongooseDocumentArray) {
+ val.forEach(function(item) {
+ item && item.__parentArray && (item.__parentArray = val);
+ });
+ }
+
+ // Small hack for gh-1638: if we're overwriting the entire array, ignore
+ // paths that were modified before the array overwrite
+ this.$__.activePaths.forEach(function(modifiedPath) {
+ if (modifiedPath.startsWith(path + '.')) {
+ _this.$__.activePaths.ignore(modifiedPath);
+ }
+ });
+ }
+ }
+
+ let obj = this._doc;
+ let i = 0;
+ const l = parts.length;
+ let cur = '';
+
+ for (; i < l; i++) {
+ const next = i + 1;
+ const last = next === l;
+ cur += (cur ? '.' + parts[i] : parts[i]);
+ if (specialProperties.has(parts[i])) {
+ return;
+ }
+
+ if (last) {
+ if (obj instanceof Map) {
+ obj.set(parts[i], val);
+ } else {
+ obj[parts[i]] = val;
+ }
+ } else {
+ if (utils.isPOJO(obj[parts[i]])) {
+ obj = obj[parts[i]];
+ } else if (obj[parts[i]] && obj[parts[i]] instanceof Embedded) {
+ obj = obj[parts[i]];
+ } else if (obj[parts[i]] && obj[parts[i]].$isSingleNested) {
+ obj = obj[parts[i]];
+ } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
+ obj = obj[parts[i]];
+ } else {
+ obj[parts[i]] = obj[parts[i]] || {};
+ obj = obj[parts[i]];
+ }
+ }
+ }
+};
+
+/**
+ * Gets a raw value from a path (no getters)
+ *
+ * @param {String} path
+ * @api private
+ */
+
+Document.prototype.$__getValue = function(path) {
+ return utils.getValue(path, this._doc);
+};
+
+/**
+ * Sets a raw value for a path (no casting, setters, transformations)
+ *
+ * @param {String} path
+ * @param {Object} value
+ * @api private
+ */
+
+Document.prototype.$__setValue = function(path, val) {
+ utils.setValue(path, val, this._doc);
+ return this;
+};
+
+/**
+ * Returns the value of a path.
+ *
+ * ####Example
+ *
+ * // path
+ * doc.get('age') // 47
+ *
+ * // dynamic casting to a string
+ * doc.get('age', String) // "47"
+ *
+ * @param {String} path
+ * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
+ * @param {Object} [options]
+ * @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
+ * @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
+ * @api public
+ */
+
+Document.prototype.get = function(path, type, options) {
+ let adhoc;
+ options = options || {};
+ if (type) {
+ adhoc = this.schema.interpretAsType(path, type, this.schema.options);
+ }
+
+ let schema = this.$__path(path);
+ if (schema == null) {
+ schema = this.schema.virtualpath(path);
+ }
+ if (schema instanceof MixedSchema) {
+ const virtual = this.schema.virtualpath(path);
+ if (virtual != null) {
+ schema = virtual;
+ }
+ }
+ const pieces = path.split('.');
+ let obj = this._doc;
+
+ if (schema instanceof VirtualType) {
+ if (schema.getters.length === 0) {
+ return void 0;
+ }
+ return schema.applyGetters(null, this);
+ }
+
+ // Might need to change path for top-level alias
+ if (typeof this.schema.aliases[pieces[0]] == 'string') {
+ pieces[0] = this.schema.aliases[pieces[0]];
+ }
+
+ for (let i = 0, l = pieces.length; i < l; i++) {
+ if (obj && obj._doc) {
+ obj = obj._doc;
+ }
+
+ if (obj == null) {
+ obj = void 0;
+ } else if (obj instanceof Map) {
+ obj = obj.get(pieces[i], { getters: false });
+ } else if (i === l - 1) {
+ obj = utils.getValue(pieces[i], obj);
+ } else {
+ obj = obj[pieces[i]];
+ }
+ }
+
+ if (adhoc) {
+ obj = adhoc.cast(obj);
+ }
+
+ if (schema != null && options.getters !== false) {
+ obj = schema.applyGetters(obj, this);
+ } else if (this.schema.nested[path] && options.virtuals) {
+ // Might need to apply virtuals if this is a nested path
+ return applyVirtuals(this, utils.clone(obj) || {}, { path: path });
+ }
+
+ return obj;
+};
+
+/*!
+ * ignore
+ */
+
+Document.prototype[getSymbol] = Document.prototype.get;
+
+/**
+ * Returns the schematype for the given `path`.
+ *
+ * @param {String} path
+ * @api private
+ * @method $__path
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__path = function(path) {
+ const adhocs = this.$__.adhocPaths;
+ const adhocType = adhocs && adhocs.hasOwnProperty(path) ? adhocs[path] : null;
+
+ if (adhocType) {
+ return adhocType;
+ }
+ return this.schema.path(path);
+};
+
+/**
+ * Marks the path as having pending changes to write to the db.
+ *
+ * _Very helpful when using [Mixed](./schematypes.html#mixed) types._
+ *
+ * ####Example:
+ *
+ * doc.mixed.type = 'changed';
+ * doc.markModified('mixed.type');
+ * doc.save() // changes to mixed.type are now persisted
+ *
+ * @param {String} path the path to mark modified
+ * @param {Document} [scope] the scope to run validators with
+ * @api public
+ */
+
+Document.prototype.markModified = function(path, scope) {
+ this.$__.activePaths.modify(path);
+ if (scope != null && !this.ownerDocument) {
+ this.$__.pathsToScopes[path] = scope;
+ }
+};
+
+/**
+ * Clears the modified state on the specified path.
+ *
+ * ####Example:
+ *
+ * doc.foo = 'bar';
+ * doc.unmarkModified('foo');
+ * doc.save(); // changes to foo will not be persisted
+ *
+ * @param {String} path the path to unmark modified
+ * @api public
+ */
+
+Document.prototype.unmarkModified = function(path) {
+ this.$__.activePaths.init(path);
+ delete this.$__.pathsToScopes[path];
+};
+
+/**
+ * Don't run validation on this path or persist changes to this path.
+ *
+ * ####Example:
+ *
+ * doc.foo = null;
+ * doc.$ignore('foo');
+ * doc.save(); // changes to foo will not be persisted and validators won't be run
+ *
+ * @memberOf Document
+ * @instance
+ * @method $ignore
+ * @param {String} path the path to ignore
+ * @api public
+ */
+
+Document.prototype.$ignore = function(path) {
+ this.$__.activePaths.ignore(path);
+};
+
+/**
+ * Returns the list of paths that have been directly modified. A direct
+ * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
+ * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
+ *
+ * A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
+ * because a child of `a` was directly modified.
+ *
+ * ####Example
+ * const schema = new Schema({ foo: String, nested: { bar: String } });
+ * const Model = mongoose.model('Test', schema);
+ * await Model.create({ foo: 'original', nested: { bar: 'original' } });
+ *
+ * const doc = await Model.findOne();
+ * doc.nested.bar = 'modified';
+ * doc.directModifiedPaths(); // ['nested.bar']
+ * doc.modifiedPaths(); // ['nested', 'nested.bar']
+ *
+ * @return {Array}
+ * @api public
+ */
+
+Document.prototype.directModifiedPaths = function() {
+ return Object.keys(this.$__.activePaths.states.modify);
+};
+
+/**
+ * Returns true if the given path is nullish or only contains empty objects.
+ * Useful for determining whether this subdoc will get stripped out by the
+ * [minimize option](/docs/guide.html#minimize).
+ *
+ * ####Example:
+ * const schema = new Schema({ nested: { foo: String } });
+ * const Model = mongoose.model('Test', schema);
+ * const doc = new Model({});
+ * doc.$isEmpty('nested'); // true
+ * doc.nested.$isEmpty(); // true
+ *
+ * doc.nested.foo = 'bar';
+ * doc.$isEmpty('nested'); // false
+ * doc.nested.$isEmpty(); // false
+ *
+ * @memberOf Document
+ * @instance
+ * @api public
+ * @method $isEmpty
+ * @return {Boolean}
+ */
+
+Document.prototype.$isEmpty = function(path) {
+ const isEmptyOptions = {
+ minimize: true,
+ virtuals: false,
+ getters: false,
+ transform: false
+ };
+
+ if (arguments.length > 0) {
+ const v = this.get(path);
+ if (v == null) {
+ return true;
+ }
+ if (typeof v !== 'object') {
+ return false;
+ }
+ if (utils.isPOJO(v)) {
+ return _isEmpty(v);
+ }
+ return Object.keys(v.toObject(isEmptyOptions)).length === 0;
+ }
+
+ return Object.keys(this.toObject(isEmptyOptions)).length === 0;
+};
+
+function _isEmpty(v) {
+ if (v == null) {
+ return true;
+ }
+ if (typeof v !== 'object' || Array.isArray(v)) {
+ return false;
+ }
+ for (const key of Object.keys(v)) {
+ if (!_isEmpty(v[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Returns the list of paths that have been modified.
+ *
+ * @param {Object} [options]
+ * @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`.
+ * @return {Array}
+ * @api public
+ */
+
+Document.prototype.modifiedPaths = function(options) {
+ options = options || {};
+ const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
+ const _this = this;
+ return directModifiedPaths.reduce(function(list, path) {
+ const parts = path.split('.');
+ list = list.concat(parts.reduce(function(chains, part, i) {
+ return chains.concat(parts.slice(0, i).concat(part).join('.'));
+ }, []).filter(function(chain) {
+ return (list.indexOf(chain) === -1);
+ }));
+
+ if (!options.includeChildren) {
+ return list;
+ }
+
+ let cur = _this.get(path);
+ if (cur != null && typeof cur === 'object') {
+ if (cur._doc) {
+ cur = cur._doc;
+ }
+ if (Array.isArray(cur)) {
+ const len = cur.length;
+ for (let i = 0; i < len; ++i) {
+ if (list.indexOf(path + '.' + i) === -1) {
+ list.push(path + '.' + i);
+ if (cur[i] != null && cur[i].$__) {
+ const modified = cur[i].modifiedPaths();
+ for (const childPath of modified) {
+ list.push(path + '.' + i + '.' + childPath);
+ }
+ }
+ }
+ }
+ } else {
+ Object.keys(cur).
+ filter(function(key) {
+ return list.indexOf(path + '.' + key) === -1;
+ }).
+ forEach(function(key) {
+ list.push(path + '.' + key);
+ });
+ }
+ }
+
+ return list;
+ }, []);
+};
+
+Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
+
+/**
+ * Returns true if this document was modified, else false.
+ *
+ * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
+ *
+ * ####Example
+ *
+ * doc.set('documents.0.title', 'changed');
+ * doc.isModified() // true
+ * doc.isModified('documents') // true
+ * doc.isModified('documents.0.title') // true
+ * doc.isModified('documents otherProp') // true
+ * doc.isDirectModified('documents') // false
+ *
+ * @param {String} [path] optional
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.isModified = function(paths, modifiedPaths) {
+ if (paths) {
+ if (!Array.isArray(paths)) {
+ paths = paths.split(' ');
+ }
+ const modified = modifiedPaths || this[documentModifiedPaths]();
+ const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
+ const isModifiedChild = paths.some(function(path) {
+ return !!~modified.indexOf(path);
+ });
+
+ return isModifiedChild || paths.some(function(path) {
+ return directModifiedPaths.some(function(mod) {
+ return mod === path || path.startsWith(mod + '.');
+ });
+ });
+ }
+
+ return this.$__.activePaths.some('modify');
+};
+
+Document.prototype[documentIsModified] = Document.prototype.isModified;
+
+/**
+ * Checks if a path is set to its default.
+ *
+ * ####Example
+ *
+ * MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
+ * const m = new MyModel();
+ * m.$isDefault('name'); // true
+ *
+ * @memberOf Document
+ * @instance
+ * @method $isDefault
+ * @param {String} [path]
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.$isDefault = function(path) {
+ return (path in this.$__.activePaths.states.default);
+};
+
+/**
+ * Getter/setter, determines whether the document was removed or not.
+ *
+ * ####Example:
+ * product.remove(function (err, product) {
+ * product.$isDeleted(); // true
+ * product.remove(); // no-op, doesn't send anything to the db
+ *
+ * product.$isDeleted(false);
+ * product.$isDeleted(); // false
+ * product.remove(); // will execute a remove against the db
+ * })
+ *
+ * @param {Boolean} [val] optional, overrides whether mongoose thinks the doc is deleted
+ * @return {Boolean} whether mongoose thinks this doc is deleted.
+ * @method $isDeleted
+ * @memberOf Document
+ * @instance
+ * @api public
+ */
+
+Document.prototype.$isDeleted = function(val) {
+ if (arguments.length === 0) {
+ return !!this.$__.isDeleted;
+ }
+
+ this.$__.isDeleted = !!val;
+ return this;
+};
+
+/**
+ * Returns true if `path` was directly set and modified, else false.
+ *
+ * ####Example
+ *
+ * doc.set('documents.0.title', 'changed');
+ * doc.isDirectModified('documents.0.title') // true
+ * doc.isDirectModified('documents') // false
+ *
+ * @param {String} path
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.isDirectModified = function(path) {
+ return (path in this.$__.activePaths.states.modify);
+};
+
+/**
+ * Checks if `path` was initialized.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.isInit = function(path) {
+ return (path in this.$__.activePaths.states.init);
+};
+
+/**
+ * Checks if `path` was selected in the source query which initialized this document.
+ *
+ * ####Example
+ *
+ * Thing.findOne().select('name').exec(function (err, doc) {
+ * doc.isSelected('name') // true
+ * doc.isSelected('age') // false
+ * })
+ *
+ * @param {String} path
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.isSelected = function isSelected(path) {
+ if (this.$__.selected) {
+ if (path === '_id') {
+ return this.$__.selected._id !== 0;
+ }
+
+ const paths = Object.keys(this.$__.selected);
+ let i = paths.length;
+ let inclusive = null;
+ let cur;
+
+ if (i === 1 && paths[0] === '_id') {
+ // only _id was selected.
+ return this.$__.selected._id === 0;
+ }
+
+ while (i--) {
+ cur = paths[i];
+ if (cur === '_id') {
+ continue;
+ }
+ if (!isDefiningProjection(this.$__.selected[cur])) {
+ continue;
+ }
+ inclusive = !!this.$__.selected[cur];
+ break;
+ }
+
+ if (inclusive === null) {
+ return true;
+ }
+
+ if (path in this.$__.selected) {
+ return inclusive;
+ }
+
+ i = paths.length;
+ const pathDot = path + '.';
+
+ while (i--) {
+ cur = paths[i];
+ if (cur === '_id') {
+ continue;
+ }
+
+ if (cur.startsWith(pathDot)) {
+ return inclusive || cur !== pathDot;
+ }
+
+ if (pathDot.startsWith(cur + '.')) {
+ return inclusive;
+ }
+ }
+
+ return !inclusive;
+ }
+
+ return true;
+};
+
+Document.prototype[documentIsSelected] = Document.prototype.isSelected;
+
+/**
+ * Checks if `path` was explicitly selected. If no projection, always returns
+ * true.
+ *
+ * ####Example
+ *
+ * Thing.findOne().select('nested.name').exec(function (err, doc) {
+ * doc.isDirectSelected('nested.name') // true
+ * doc.isDirectSelected('nested.otherName') // false
+ * doc.isDirectSelected('nested') // false
+ * })
+ *
+ * @param {String} path
+ * @return {Boolean}
+ * @api public
+ */
+
+Document.prototype.isDirectSelected = function isDirectSelected(path) {
+ if (this.$__.selected) {
+ if (path === '_id') {
+ return this.$__.selected._id !== 0;
+ }
+
+ const paths = Object.keys(this.$__.selected);
+ let i = paths.length;
+ let inclusive = null;
+ let cur;
+
+ if (i === 1 && paths[0] === '_id') {
+ // only _id was selected.
+ return this.$__.selected._id === 0;
+ }
+
+ while (i--) {
+ cur = paths[i];
+ if (cur === '_id') {
+ continue;
+ }
+ if (!isDefiningProjection(this.$__.selected[cur])) {
+ continue;
+ }
+ inclusive = !!this.$__.selected[cur];
+ break;
+ }
+
+ if (inclusive === null) {
+ return true;
+ }
+
+ if (path in this.$__.selected) {
+ return inclusive;
+ }
+
+ return !inclusive;
+ }
+
+ return true;
+};
+
+/**
+ * Executes registered validation rules for this document.
+ *
+ * ####Note:
+ *
+ * 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`.
+ *
+ * ####Example:
+ *
+ * doc.validate(function (err) {
+ * if (err) handleError(err);
+ * else // validation passed
+ * });
+ *
+ * @param {Array|String} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list.
+ * @param {Object} [options] internal options
+ * @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths.
+ * @param {Function} [callback] optional callback called after validation completes, passing an error if one occurred
+ * @return {Promise} Promise
+ * @api public
+ */
+
+Document.prototype.validate = function(pathsToValidate, options, callback) {
+ let parallelValidate;
+ this.$op = 'validate';
+
+ if (this.ownerDocument != null) {
+ // Skip parallel validate check for subdocuments
+ } else if (this.$__.validating) {
+ parallelValidate = new ParallelValidateError(this, {
+ parentStack: options && options.parentStack,
+ conflictStack: this.$__.validating.stack
+ });
+ } else {
+ this.$__.validating = new ParallelValidateError(this, { parentStack: options && options.parentStack });
+ }
+
+ if (typeof pathsToValidate === 'function') {
+ callback = pathsToValidate;
+ options = null;
+ pathsToValidate = null;
+ } else if (typeof options === 'function') {
+ callback = options;
+ options = pathsToValidate;
+ pathsToValidate = null;
+ }
+
+ return promiseOrCallback(callback, cb => {
+ if (parallelValidate != null) {
+ return cb(parallelValidate);
+ }
+
+ this.$__validate(pathsToValidate, options, (error) => {
+ this.$op = null;
+ cb(error);
+ });
+ }, this.constructor.events);
+};
+
+/*!
+ * ignore
+ */
+
+function _evaluateRequiredFunctions(doc) {
+ Object.keys(doc.$__.activePaths.states.require).forEach(path => {
+ const p = doc.schema.path(path);
+
+ if (p != null && typeof p.originalRequiredValue === 'function') {
+ doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc);
+ }
+ });
+}
+
+/*!
+ * ignore
+ */
+
+function _getPathsToValidate(doc) {
+ const skipSchemaValidators = {};
+
+ _evaluateRequiredFunctions(doc);
+
+ // only validate required fields when necessary
+ let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
+ if (!doc.isSelected(path) && !doc.isModified(path)) {
+ return false;
+ }
+ if (path in doc.$__.cachedRequired) {
+ return doc.$__.cachedRequired[path];
+ }
+ return true;
+ }));
+
+
+ Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths);
+ Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths);
+ Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths);
+ function addToPaths(p) { paths.add(p); }
+
+ const subdocs = doc.$__getAllSubdocs();
+ const modifiedPaths = doc.modifiedPaths();
+ for (const subdoc of subdocs) {
+ if (subdoc.$basePath) {
+ // Remove child paths for now, because we'll be validating the whole
+ // subdoc
+ for (const p of paths) {
+ if (p === null || p.startsWith(subdoc.$basePath + '.')) {
+ paths.delete(p);
+ }
+ }
+
+ if (doc.isModified(subdoc.$basePath, modifiedPaths) &&
+ !doc.isDirectModified(subdoc.$basePath) &&
+ !doc.$isDefault(subdoc.$basePath)) {
+ paths.add(subdoc.$basePath);
+
+ skipSchemaValidators[subdoc.$basePath] = true;
+ }
+ }
+ }
+
+ // from here on we're not removing items from paths
+
+ // gh-661: if a whole array is modified, make sure to run validation on all
+ // the children as well
+ for (const path of paths) {
+ const _pathType = doc.schema.path(path);
+ if (!_pathType ||
+ !_pathType.$isMongooseArray ||
+ // To avoid potential performance issues, skip doc arrays whose children
+ // are not required. `getPositionalPathType()` may be slow, so avoid
+ // it unless we have a case of #6364
+ (_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) {
+ continue;
+ }
+
+ const val = doc.$__getValue(path);
+ _pushNestedArrayPaths(val, paths, path);
+ }
+
+ function _pushNestedArrayPaths(val, paths, path) {
+ if (val != null) {
+ const numElements = val.length;
+ for (let j = 0; j < numElements; ++j) {
+ if (Array.isArray(val[j])) {
+ _pushNestedArrayPaths(val[j], paths, path + '.' + j);
+ } else {
+ paths.add(path + '.' + j);
+ }
+ }
+ }
+ }
+
+ const flattenOptions = { skipArrays: true };
+ for (const pathToCheck of paths) {
+ if (doc.schema.nested[pathToCheck]) {
+ let _v = doc.$__getValue(pathToCheck);
+ if (isMongooseObject(_v)) {
+ _v = _v.toObject({ transform: false });
+ }
+ const flat = flatten(_v, pathToCheck, flattenOptions, doc.schema);
+ Object.keys(flat).forEach(addToPaths);
+ }
+ }
+
+
+ for (const path of paths) {
+ // Single nested paths (paths embedded under single nested subdocs) will
+ // be validated on their own when we call `validate()` on the subdoc itself.
+ // Re: gh-8468
+ if (doc.schema.singleNestedPaths.hasOwnProperty(path)) {
+ paths.delete(path);
+ continue;
+ }
+ const _pathType = doc.schema.path(path);
+ if (!_pathType || !_pathType.$isSchemaMap) {
+ continue;
+ }
+
+ const val = doc.$__getValue(path);
+ if (val == null) {
+ continue;
+ }
+ for (const key of val.keys()) {
+ paths.add(path + '.' + key);
+ }
+ }
+
+ paths = Array.from(paths);
+ return [paths, skipSchemaValidators];
+}
+
+/*!
+ * ignore
+ */
+
+Document.prototype.$__validate = function(pathsToValidate, options, callback) {
+ if (typeof pathsToValidate === 'function') {
+ callback = pathsToValidate;
+ options = null;
+ pathsToValidate = null;
+ } else if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
+
+ const hasValidateModifiedOnlyOption = options &&
+ (typeof options === 'object') &&
+ ('validateModifiedOnly' in options);
+
+ let shouldValidateModifiedOnly;
+ if (hasValidateModifiedOnlyOption) {
+ shouldValidateModifiedOnly = !!options.validateModifiedOnly;
+ } else {
+ shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
+ }
+
+ const _this = this;
+ const _complete = () => {
+ let validationError = this.$__.validationError;
+ this.$__.validationError = undefined;
+
+ if (shouldValidateModifiedOnly && validationError != null) {
+ // Remove any validation errors that aren't from modified paths
+ const errors = Object.keys(validationError.errors);
+ for (const errPath of errors) {
+ if (!this.isModified(errPath)) {
+ delete validationError.errors[errPath];
+ }
+ }
+ if (Object.keys(validationError.errors).length === 0) {
+ validationError = void 0;
+ }
+ }
+
+ this.$__.cachedRequired = {};
+ this.emit('validate', _this);
+ this.constructor.emit('validate', _this);
+
+ this.$__.validating = null;
+ if (validationError) {
+ for (const key in validationError.errors) {
+ // Make sure cast errors persist
+ if (!this[documentArrayParent] &&
+ validationError.errors[key] instanceof MongooseError.CastError) {
+ this.invalidate(key, validationError.errors[key]);
+ }
+ }
+
+ return validationError;
+ }
+ };
+
+ // only validate required fields when necessary
+ const pathDetails = _getPathsToValidate(this);
+ let paths = shouldValidateModifiedOnly ?
+ pathDetails[0].filter((path) => this.isModified(path)) :
+ pathDetails[0];
+ const skipSchemaValidators = pathDetails[1];
+
+ if (Array.isArray(pathsToValidate)) {
+ paths = _handlePathsToValidate(paths, pathsToValidate);
+ }
+
+ if (paths.length === 0) {
+ return process.nextTick(function() {
+ const error = _complete();
+ if (error) {
+ return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) {
+ callback(error);
+ });
+ }
+ callback(null, _this);
+ });
+ }
+
+ const validated = {};
+ let total = 0;
+
+ const complete = function() {
+ const error = _complete();
+ if (error) {
+ return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) {
+ callback(error);
+ });
+ }
+ callback(null, _this);
+ };
+
+ const validatePath = function(path) {
+ if (path == null || validated[path]) {
+ return;
+ }
+
+ validated[path] = true;
+ total++;
+
+ process.nextTick(function() {
+ const schemaType = _this.schema.path(path);
+
+ if (!schemaType) {
+ return --total || complete();
+ }
+
+ // If user marked as invalid or there was a cast error, don't validate
+ if (!_this.$isValid(path)) {
+ --total || complete();
+ return;
+ }
+
+ let val = _this.$__getValue(path);
+
+ // If you `populate()` and get back a null value, required validators
+ // shouldn't fail (gh-8018). We should always fall back to the populated
+ // value.
+ let pop;
+ if (val == null && (pop = _this.populated(path))) {
+ val = pop;
+ }
+ const scope = path in _this.$__.pathsToScopes ?
+ _this.$__.pathsToScopes[path] :
+ _this;
+
+ const doValidateOptions = {
+ skipSchemaValidators: skipSchemaValidators[path],
+ path: path
+ };
+ schemaType.doValidate(val, function(err) {
+ if (err && (!schemaType.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
+ if (schemaType.$isSingleNested &&
+ err instanceof ValidationError &&
+ schemaType.schema.options.storeSubdocValidationError === false) {
+ return --total || complete();
+ }
+ _this.invalidate(path, err, undefined, true);
+ }
+ --total || complete();
+ }, scope, doValidateOptions);
+ });
+ };
+
+ const numPaths = paths.length;
+ for (let i = 0; i < numPaths; ++i) {
+ validatePath(paths[i]);
+ }
+};
+
+/*!
+ * ignore
+ */
+
+function _handlePathsToValidate(paths, pathsToValidate) {
+ const _pathsToValidate = new Set(pathsToValidate);
+ const parentPaths = new Map([]);
+ for (const path of pathsToValidate) {
+ if (path.indexOf('.') === -1) {
+ continue;
+ }
+ const pieces = path.split('.');
+ let cur = pieces[0];
+ for (let i = 1; i < pieces.length; ++i) {
+ // Since we skip subpaths under single nested subdocs to
+ // avoid double validation, we need to add back the
+ // single nested subpath if the user asked for it (gh-8626)
+ parentPaths.set(cur, path);
+ cur = cur + '.' + pieces[i];
+ }
+ }
+
+ const ret = [];
+ for (const path of paths) {
+ if (_pathsToValidate.has(path)) {
+ ret.push(path);
+ } else if (parentPaths.has(path)) {
+ ret.push(parentPaths.get(path));
+ }
+ }
+ return ret;
+}
+
+/**
+ * Executes registered validation rules (skipping asynchronous validators) for this document.
+ *
+ * ####Note:
+ *
+ * This method is useful if you need synchronous validation.
+ *
+ * ####Example:
+ *
+ * const err = doc.validateSync();
+ * if (err) {
+ * handleError(err);
+ * } else {
+ * // validation passed
+ * }
+ *
+ * @param {Array|string} pathsToValidate only validate the given paths
+ * @param {Object} [options] options for validation
+ * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
+ * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
+ * @api public
+ */
+
+Document.prototype.validateSync = function(pathsToValidate, options) {
+ const _this = this;
+
+ const hasValidateModifiedOnlyOption = options &&
+ (typeof options === 'object') &&
+ ('validateModifiedOnly' in options);
+
+ let shouldValidateModifiedOnly;
+ if (hasValidateModifiedOnlyOption) {
+ shouldValidateModifiedOnly = !!options.validateModifiedOnly;
+ } else {
+ shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
+ }
+
+ if (typeof pathsToValidate === 'string') {
+ pathsToValidate = pathsToValidate.split(' ');
+ }
+
+ // only validate required fields when necessary
+ const pathDetails = _getPathsToValidate(this);
+ let paths = shouldValidateModifiedOnly ?
+ pathDetails[0].filter((path) => this.isModified(path)) :
+ pathDetails[0];
+ const skipSchemaValidators = pathDetails[1];
+
+ if (Array.isArray(pathsToValidate)) {
+ paths = _handlePathsToValidate(paths, pathsToValidate);
+ }
+
+ const validating = {};
+
+ paths.forEach(function(path) {
+ if (validating[path]) {
+ return;
+ }
+
+ validating[path] = true;
+
+ const p = _this.schema.path(path);
+ if (!p) {
+ return;
+ }
+ if (!_this.$isValid(path)) {
+ return;
+ }
+
+ const val = _this.$__getValue(path);
+ const err = p.doValidateSync(val, _this, {
+ skipSchemaValidators: skipSchemaValidators[path],
+ path: path
+ });
+ if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
+ if (p.$isSingleNested &&
+ err instanceof ValidationError &&
+ p.schema.options.storeSubdocValidationError === false) {
+ return;
+ }
+ _this.invalidate(path, err, undefined, true);
+ }
+ });
+
+ const err = _this.$__.validationError;
+ _this.$__.validationError = undefined;
+ _this.emit('validate', _this);
+ _this.constructor.emit('validate', _this);
+
+ if (err) {
+ for (const key in err.errors) {
+ // Make sure cast errors persist
+ if (err.errors[key] instanceof MongooseError.CastError) {
+ _this.invalidate(key, err.errors[key]);
+ }
+ }
+ }
+
+ return err;
+};
+
+/**
+ * Marks a path as invalid, causing validation to fail.
+ *
+ * The `errorMsg` argument will become the message of the `ValidationError`.
+ *
+ * The `value` argument (if passed) will be available through the `ValidationError.value` property.
+ *
+ * doc.invalidate('size', 'must be less than 20', 14);
+
+ * doc.validate(function (err) {
+ * console.log(err)
+ * // prints
+ * { message: 'Validation failed',
+ * name: 'ValidationError',
+ * errors:
+ * { size:
+ * { message: 'must be less than 20',
+ * name: 'ValidatorError',
+ * path: 'size',
+ * type: 'user defined',
+ * value: 14 } } }
+ * })
+ *
+ * @param {String} path the field to invalidate. For array elements, use the `array.i.field` syntax, where `i` is the 0-based index in the array.
+ * @param {String|Error} errorMsg the error which states the reason `path` was invalid
+ * @param {Object|String|Number|any} value optional invalid value
+ * @param {String} [kind] optional `kind` property for the error
+ * @return {ValidationError} the current ValidationError, with all currently invalidated paths
+ * @api public
+ */
+
+Document.prototype.invalidate = function(path, err, val, kind) {
+ if (!this.$__.validationError) {
+ this.$__.validationError = new ValidationError(this);
+ }
+
+ if (this.$__.validationError.errors[path]) {
+ return;
+ }
+
+ if (!err || typeof err === 'string') {
+ err = new ValidatorError({
+ path: path,
+ message: err,
+ type: kind || 'user defined',
+ value: val
+ });
+ }
+
+ if (this.$__.validationError === err) {
+ return this.$__.validationError;
+ }
+
+ this.$__.validationError.addError(path, err);
+ return this.$__.validationError;
+};
+
+/**
+ * Marks a path as valid, removing existing validation errors.
+ *
+ * @param {String} path the field to mark as valid
+ * @api public
+ * @memberOf Document
+ * @instance
+ * @method $markValid
+ */
+
+Document.prototype.$markValid = function(path) {
+ if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
+ return;
+ }
+
+ delete this.$__.validationError.errors[path];
+ if (Object.keys(this.$__.validationError.errors).length === 0) {
+ this.$__.validationError = null;
+ }
+};
+
+/*!
+ * ignore
+ */
+
+function _markValidSubpaths(doc, path) {
+ if (!doc.$__.validationError) {
+ return;
+ }
+
+ const keys = Object.keys(doc.$__.validationError.errors);
+ for (const key of keys) {
+ if (key.startsWith(path + '.')) {
+ delete doc.$__.validationError.errors[key];
+ }
+ }
+ if (Object.keys(doc.$__.validationError.errors).length === 0) {
+ doc.$__.validationError = null;
+ }
+}
+
+/*!
+ * ignore
+ */
+
+function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
+ const schema = schematype.schema;
+ if (schema == null) {
+ return;
+ }
+
+ for (const key of Object.keys(schema.paths)) {
+ const path = schema.paths[key];
+ if (path.$immutableSetter == null) {
+ continue;
+ }
+ const oldVal = priorVal == null ? void 0 : priorVal.$__getValue(key);
+ // Calling immutableSetter with `oldVal` even though it expects `newVal`
+ // is intentional. That's because `$immutableSetter` compares its param
+ // to the current value.
+ path.$immutableSetter.call(subdoc, oldVal);
+ }
+}
+
+/**
+ * Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`,
+ * 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.
+ *
+ * ####Example:
+ *
+ * product.sold = Date.now();
+ * product = await product.save();
+ *
+ * If save is successful, the returned promise will fulfill with the document
+ * saved.
+ *
+ * ####Example:
+ *
+ * const newProduct = await product.save();
+ * newProduct === product; // true
+ *
+ * @param {Object} [options] options optional options
+ * @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).
+ * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
+ * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
+ * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
+ * @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)
+ * @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)
+ * @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).
+ * @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)
+ * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`.
+ * @param {Function} [fn] optional callback
+ * @method save
+ * @memberOf Document
+ * @instance
+ * @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).
+ * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
+ * @api public
+ * @see middleware http://mongoosejs.com/docs/middleware.html
+ */
+
+/**
+ * Checks if a path is invalid
+ *
+ * @param {String} path the field to check
+ * @method $isValid
+ * @memberOf Document
+ * @instance
+ * @api private
+ */
+
+Document.prototype.$isValid = function(path) {
+ return !this.$__.validationError || !this.$__.validationError.errors[path];
+};
+
+/**
+ * Resets the internal modified state of this document.
+ *
+ * @api private
+ * @return {Document}
+ * @method $__reset
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__reset = function reset() {
+ let _this = this;
+ DocumentArray || (DocumentArray = require('./types/documentarray'));
+
+ this.$__.activePaths
+ .map('init', 'modify', function(i) {
+ return _this.$__getValue(i);
+ })
+ .filter(function(val) {
+ return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
+ })
+ .forEach(function(array) {
+ let i = array.length;
+ while (i--) {
+ const doc = array[i];
+ if (!doc) {
+ continue;
+ }
+ doc.$__reset();
+ }
+
+ _this.$__.activePaths.init(array.$path());
+
+ array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol];
+ array[arrayAtomicsSymbol] = {};
+ });
+
+ this.$__.activePaths.
+ map('init', 'modify', function(i) {
+ return _this.$__getValue(i);
+ }).
+ filter(function(val) {
+ return val && val.$isSingleNested;
+ }).
+ forEach(function(doc) {
+ doc.$__reset();
+ _this.$__.activePaths.init(doc.$basePath);
+ });
+
+ // clear atomics
+ this.$__dirty().forEach(function(dirt) {
+ const type = dirt.value;
+
+ if (type && type[arrayAtomicsSymbol]) {
+ type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol];
+ type[arrayAtomicsSymbol] = {};
+ }
+ });
+
+ this.$__.backup = {};
+ this.$__.backup.activePaths = {
+ modify: Object.assign({}, this.$__.activePaths.states.modify),
+ default: Object.assign({}, this.$__.activePaths.states.default)
+ };
+ this.$__.backup.validationError = this.$__.validationError;
+ this.$__.backup.errors = this.errors;
+
+ // Clear 'dirty' cache
+ this.$__.activePaths.clear('modify');
+ this.$__.activePaths.clear('default');
+ this.$__.validationError = undefined;
+ this.errors = undefined;
+ _this = this;
+ this.schema.requiredPaths().forEach(function(path) {
+ _this.$__.activePaths.require(path);
+ });
+
+ return this;
+};
+
+/*!
+ * ignore
+ */
+
+Document.prototype.$__undoReset = function $__undoReset() {
+ if (this.$__.backup == null || this.$__.backup.activePaths == null) {
+ return;
+ }
+
+ this.$__.activePaths.states.modify = this.$__.backup.activePaths.modify;
+ this.$__.activePaths.states.default = this.$__.backup.activePaths.default;
+
+ this.$__.validationError = this.$__.backup.validationError;
+ this.errors = this.$__.backup.errors;
+
+ for (const dirt of this.$__dirty()) {
+ const type = dirt.value;
+
+ if (type && type[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
+ type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol];
+ }
+ }
+
+ for (const subdoc of this.$__getAllSubdocs()) {
+ subdoc.$__undoReset();
+ }
+};
+
+/**
+ * Returns this documents dirty paths / vals.
+ *
+ * @api private
+ * @method $__dirty
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__dirty = function() {
+ const _this = this;
+
+ let all = this.$__.activePaths.map('modify', function(path) {
+ return {
+ path: path,
+ value: _this.$__getValue(path),
+ schema: _this.$__path(path)
+ };
+ });
+
+ // gh-2558: if we had to set a default and the value is not undefined,
+ // we have to save as well
+ all = all.concat(this.$__.activePaths.map('default', function(path) {
+ if (path === '_id' || _this.$__getValue(path) == null) {
+ return;
+ }
+ return {
+ path: path,
+ value: _this.$__getValue(path),
+ schema: _this.$__path(path)
+ };
+ }));
+
+ // Sort dirty paths in a flat hierarchy.
+ all.sort(function(a, b) {
+ return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
+ });
+
+ // Ignore "foo.a" if "foo" is dirty already.
+ const minimal = [];
+ let lastPath;
+ let top;
+
+ all.forEach(function(item) {
+ if (!item) {
+ return;
+ }
+ if (lastPath == null || item.path.indexOf(lastPath) !== 0) {
+ lastPath = item.path + '.';
+ minimal.push(item);
+ top = item;
+ } else if (top != null &&
+ top.value != null &&
+ top.value[arrayAtomicsSymbol] != null &&
+ top.value.hasAtomics()) {
+ // special case for top level MongooseArrays
+ // the `top` array itself and a sub path of `top` are being modified.
+ // the only way to honor all of both modifications is through a $set
+ // of entire array.
+ top.value[arrayAtomicsSymbol] = {};
+ top.value[arrayAtomicsSymbol].$set = top.value;
+ }
+ });
+
+ top = lastPath = null;
+ return minimal;
+};
+
+/**
+ * Assigns/compiles `schema` into this documents prototype.
+ *
+ * @param {Schema} schema
+ * @api private
+ * @method $__setSchema
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__setSchema = function(schema) {
+ schema.plugin(idGetter, { deduplicate: true });
+ compile(schema.tree, this, undefined, schema.options);
+
+ // Apply default getters if virtual doesn't have any (gh-6262)
+ for (const key of Object.keys(schema.virtuals)) {
+ schema.virtuals[key]._applyDefaultGetters();
+ }
+
+ this.schema = schema;
+ this[documentSchemaSymbol] = schema;
+};
+
+
+/**
+ * Get active path that were changed and are arrays
+ *
+ * @api private
+ * @method $__getArrayPathsToValidate
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__getArrayPathsToValidate = function() {
+ DocumentArray || (DocumentArray = require('./types/documentarray'));
+
+ // validate all document arrays.
+ return this.$__.activePaths
+ .map('init', 'modify', function(i) {
+ return this.$__getValue(i);
+ }.bind(this))
+ .filter(function(val) {
+ return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
+ }).reduce(function(seed, array) {
+ return seed.concat(array);
+ }, [])
+ .filter(function(doc) {
+ return doc;
+ });
+};
+
+
+/**
+ * Get all subdocs (by bfs)
+ *
+ * @api private
+ * @method $__getAllSubdocs
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__getAllSubdocs = function() {
+ DocumentArray || (DocumentArray = require('./types/documentarray'));
+ Embedded = Embedded || require('./types/embedded');
+
+ function docReducer(doc, seed, path) {
+ let val = doc;
+ if (path) {
+ if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) {
+ val = doc._doc[path];
+ } else {
+ val = doc[path];
+ }
+ }
+ if (val instanceof Embedded) {
+ seed.push(val);
+ } else if (val instanceof Map) {
+ seed = Array.from(val.keys()).reduce(function(seed, path) {
+ return docReducer(val.get(path), seed, null);
+ }, seed);
+ } else if (val && val.$isSingleNested) {
+ seed = Object.keys(val._doc).reduce(function(seed, path) {
+ return docReducer(val._doc, seed, path);
+ }, seed);
+ seed.push(val);
+ } else if (val && val.isMongooseDocumentArray) {
+ val.forEach(function _docReduce(doc) {
+ if (!doc || !doc._doc) {
+ return;
+ }
+ seed = Object.keys(doc._doc).reduce(function(seed, path) {
+ return docReducer(doc._doc, seed, path);
+ }, seed);
+ if (doc instanceof Embedded) {
+ seed.push(doc);
+ }
+ });
+ } else if (val instanceof Document && val.$__isNested) {
+ seed = Object.keys(val).reduce(function(seed, path) {
+ return docReducer(val, seed, path);
+ }, seed);
+ }
+ return seed;
+ }
+
+ const _this = this;
+ const subDocs = Object.keys(this._doc).reduce(function(seed, path) {
+ return docReducer(_this, seed, path);
+ }, []);
+
+ return subDocs;
+};
+
+/*!
+ * Runs queued functions
+ */
+
+function applyQueue(doc) {
+ const q = doc.schema && doc.schema.callQueue;
+ if (!q.length) {
+ return;
+ }
+
+ for (const pair of q) {
+ if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
+ doc[pair[0]].apply(doc, pair[1]);
+ }
+ }
+}
+
+/*!
+ * ignore
+ */
+
+Document.prototype.$__handleReject = function handleReject(err) {
+ // emit on the Model if listening
+ if (this.listeners('error').length) {
+ this.emit('error', err);
+ } else if (this.constructor.listeners && this.constructor.listeners('error').length) {
+ this.constructor.emit('error', err);
+ }
+};
+
+/**
+ * Internal helper for toObject() and toJSON() that doesn't manipulate options
+ *
+ * @api private
+ * @method $toObject
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$toObject = function(options, json) {
+ let defaultOptions = {
+ transform: true,
+ flattenDecimals: true
+ };
+
+ const path = json ? 'toJSON' : 'toObject';
+ const baseOptions = get(this, 'constructor.base.options.' + path, {});
+ const schemaOptions = get(this, 'schema.options', {});
+ // merge base default options with Schema's set default options if available.
+ // `clone` is necessary here because `utils.options` directly modifies the second input.
+ defaultOptions = utils.options(defaultOptions, clone(baseOptions));
+ defaultOptions = utils.options(defaultOptions, clone(schemaOptions[path] || {}));
+
+ // If options do not exist or is not an object, set it to empty object
+ options = utils.isPOJO(options) ? clone(options) : {};
+ options._calledWithOptions = options._calledWithOptions || clone(options);
+
+ if (!('flattenMaps' in options)) {
+ options.flattenMaps = defaultOptions.flattenMaps;
+ }
+
+ let _minimize;
+ if (options._calledWithOptions.minimize != null) {
+ _minimize = options.minimize;
+ } else if (defaultOptions.minimize != null) {
+ _minimize = defaultOptions.minimize;
+ } else {
+ _minimize = schemaOptions.minimize;
+ }
+
+ // The original options that will be passed to `clone()`. Important because
+ // `clone()` will recursively call `$toObject()` on embedded docs, so we
+ // need the original options the user passed in, plus `_isNested` and
+ // `_parentOptions` for checking whether we need to depopulate.
+ const cloneOptions = Object.assign(utils.clone(options), {
+ _isNested: true,
+ json: json,
+ minimize: _minimize
+ });
+
+ if (utils.hasUserDefinedProperty(options, 'getters')) {
+ cloneOptions.getters = options.getters;
+ }
+ if (utils.hasUserDefinedProperty(options, 'virtuals')) {
+ cloneOptions.virtuals = options.virtuals;
+ }
+
+ const depopulate = options.depopulate ||
+ get(options, '_parentOptions.depopulate', false);
+ // _isNested will only be true if this is not the top level document, we
+ // should never depopulate
+ if (depopulate && options._isNested && this.$__.wasPopulated) {
+ // populated paths that we set to a document
+ return clone(this._id, cloneOptions);
+ }
+
+ // merge default options with input options.
+ options = utils.options(defaultOptions, options);
+ options._isNested = true;
+ options.json = json;
+ options.minimize = _minimize;
+
+ cloneOptions._parentOptions = options;
+ cloneOptions._skipSingleNestedGetters = true;
+
+ const gettersOptions = Object.assign({}, cloneOptions);
+ gettersOptions._skipSingleNestedGetters = false;
+
+ // remember the root transform function
+ // to save it from being overwritten by sub-transform functions
+ const originalTransform = options.transform;
+
+ let ret = clone(this._doc, cloneOptions) || {};
+
+ if (options.getters) {
+ applyGetters(this, ret, gettersOptions);
+
+ if (options.minimize) {
+ ret = minimize(ret) || {};
+ }
+ }
+
+ if (options.virtuals || (options.getters && options.virtuals !== false)) {
+ applyVirtuals(this, ret, gettersOptions, options);
+ }
+
+ if (options.versionKey === false && this.schema.options.versionKey) {
+ delete ret[this.schema.options.versionKey];
+ }
+
+ let transform = options.transform;
+
+ // In the case where a subdocument has its own transform function, we need to
+ // check and see if the parent has a transform (options.transform) and if the
+ // child schema has a transform (this.schema.options.toObject) In this case,
+ // we need to adjust options.transform to be the child schema's transform and
+ // not the parent schema's
+ if (transform) {
+ applySchemaTypeTransforms(this, ret);
+ }
+
+ if (options.useProjection) {
+ omitDeselectedFields(this, ret);
+ }
+
+ if (transform === true || (schemaOptions.toObject && transform)) {
+ const opts = options.json ? schemaOptions.toJSON : schemaOptions.toObject;
+
+ if (opts) {
+ transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
+ }
+ } else {
+ options.transform = originalTransform;
+ }
+
+ if (typeof transform === 'function') {
+ const xformed = transform(this, ret, options);
+ if (typeof xformed !== 'undefined') {
+ ret = xformed;
+ }
+ }
+
+ return ret;
+};
+
+/**
+ * Converts this document into a plain javascript object, ready for storage in MongoDB.
+ *
+ * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
+ *
+ * ####Options:
+ *
+ * - `getters` apply all getters (path and virtual getters), defaults to false
+ * - `aliases` apply all aliases if `virtuals=true`, defaults to true
+ * - `virtuals` apply virtual getters (can override `getters` option), defaults to false
+ * - `minimize` remove empty objects, defaults to true
+ * - `transform` a transform function to apply to the resulting document before returning
+ * - `depopulate` depopulate any populated paths, replacing them with their original refs, defaults to false
+ * - `versionKey` whether to include the version key, defaults to true
+ * - `flattenMaps` convert Maps to POJOs. Useful if you want to JSON.stringify() the result of toObject(), defaults to false
+ * - `useProjection` set to `true` to omit fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
+ *
+ * ####Getters/Virtuals
+ *
+ * Example of only applying path getters
+ *
+ * doc.toObject({ getters: true, virtuals: false })
+ *
+ * Example of only applying virtual getters
+ *
+ * doc.toObject({ virtuals: true })
+ *
+ * Example of applying both path and virtual getters
+ *
+ * doc.toObject({ getters: true })
+ *
+ * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument.
+ *
+ * schema.set('toObject', { virtuals: true })
+ *
+ * ####Transform
+ *
+ * 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.
+ *
+ * Transform functions receive three arguments
+ *
+ * function (doc, ret, options) {}
+ *
+ * - `doc` The mongoose document which is being converted
+ * - `ret` The plain object representation which has been converted
+ * - `options` The options in use (either schema options or the options passed inline)
+ *
+ * ####Example
+ *
+ * // specify the transform schema option
+ * if (!schema.options.toObject) schema.options.toObject = {};
+ * schema.options.toObject.transform = function (doc, ret, options) {
+ * // remove the _id of every document before returning the result
+ * delete ret._id;
+ * return ret;
+ * }
+ *
+ * // without the transformation in the schema
+ * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
+ *
+ * // with the transformation
+ * doc.toObject(); // { name: 'Wreck-it Ralph' }
+ *
+ * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
+ *
+ * if (!schema.options.toObject) schema.options.toObject = {};
+ * schema.options.toObject.transform = function (doc, ret, options) {
+ * return { movie: ret.name }
+ * }
+ *
+ * // without the transformation in the schema
+ * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
+ *
+ * // with the transformation
+ * doc.toObject(); // { movie: 'Wreck-it Ralph' }
+ *
+ * _Note: if a transform function returns `undefined`, the return value will be ignored._
+ *
+ * Transformations may also be applied inline, overridding any transform set in the options:
+ *
+ * function xform (doc, ret, options) {
+ * return { inline: ret.name, custom: true }
+ * }
+ *
+ * // pass the transform as an inline option
+ * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
+ *
+ * If you want to skip transformations, use `transform: false`:
+ *
+ * schema.options.toObject.hide = '_id';
+ * schema.options.toObject.transform = function (doc, ret, options) {
+ * if (options.hide) {
+ * options.hide.split(' ').forEach(function (prop) {
+ * delete ret[prop];
+ * });
+ * }
+ * return ret;
+ * }
+ *
+ * const doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
+ * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
+ * doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
+ * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
+ *
+ * If you pass a transform in `toObject()` options, Mongoose will apply the transform
+ * to [subdocuments](/docs/subdocs.html) in addition to the top-level document.
+ * Similarly, `transform: false` skips transforms for all subdocuments.
+ * Note that this is behavior is different for transforms defined in the schema:
+ * if you define a transform in `schema.options.toObject.transform`, that transform
+ * will **not** apply to subdocuments.
+ *
+ * const memberSchema = new Schema({ name: String, email: String });
+ * const groupSchema = new Schema({ members: [memberSchema], name: String, email });
+ * const Group = mongoose.model('Group', groupSchema);
+ *
+ * const doc = new Group({
+ * name: 'Engineering',
+ * email: 'dev@mongoosejs.io',
+ * members: [{ name: 'Val', email: 'val@mongoosejs.io' }]
+ * });
+ *
+ * // Removes `email` from both top-level document **and** array elements
+ * // { name: 'Engineering', members: [{ name: 'Val' }] }
+ * doc.toObject({ transform: (doc, ret) => { delete ret.email; return ret; } });
+ *
+ * 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.
+ *
+ * See [schema options](/docs/guide.html#toObject) for some more details.
+ *
+ * _During save, no custom options are applied to the document before being sent to the database._
+ *
+ * @param {Object} [options]
+ * @param {Boolean} [options.getters=false] if true, apply all getters, including virtuals
+ * @param {Boolean} [options.virtuals=false] if true, apply virtuals, including aliases. Use `{ getters: true, virtuals: false }` to just apply getters, not virtuals
+ * @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`.
+ * @param {Boolean} [options.minimize=true] if true, omit any empty objects from the output
+ * @param {Function|null} [options.transform=null] if set, mongoose will call this function to allow you to transform the returned object
+ * @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.
+ * @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output
+ * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
+ * @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
+ * @return {Object} js object
+ * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.toObject = function(options) {
+ return this.$toObject(options);
+};
+
+/*!
+ * Minimizes an object, removing undefined values and empty objects
+ *
+ * @param {Object} object to minimize
+ * @return {Object}
+ */
+
+function minimize(obj) {
+ const keys = Object.keys(obj);
+ let i = keys.length;
+ let hasKeys;
+ let key;
+ let val;
+
+ while (i--) {
+ key = keys[i];
+ val = obj[key];
+
+ if (utils.isObject(val) && !Buffer.isBuffer(val)) {
+ obj[key] = minimize(val);
+ }
+
+ if (undefined === obj[key]) {
+ delete obj[key];
+ continue;
+ }
+
+ hasKeys = true;
+ }
+
+ return hasKeys
+ ? obj
+ : undefined;
+}
+
+/*!
+ * Applies virtuals properties to `json`.
+ */
+
+function applyVirtuals(self, json, options, toObjectOptions) {
+ const schema = self.schema;
+ const paths = Object.keys(schema.virtuals);
+ let i = paths.length;
+ const numPaths = i;
+ let path;
+ let assignPath;
+ let cur = self._doc;
+ let v;
+ const aliases = get(toObjectOptions, 'aliases', true);
+
+ if (!cur) {
+ return json;
+ }
+
+ options = options || {};
+ for (i = 0; i < numPaths; ++i) {
+ path = paths[i];
+
+ // Allow skipping aliases with `toObject({ virtuals: true, aliases: false })`
+ if (!aliases && schema.aliases.hasOwnProperty(path)) {
+ continue;
+ }
+
+ // We may be applying virtuals to a nested object, for example if calling
+ // `doc.nestedProp.toJSON()`. If so, the path we assign to, `assignPath`,
+ // will be a trailing substring of the `path`.
+ assignPath = path;
+ if (options.path != null) {
+ if (!path.startsWith(options.path + '.')) {
+ continue;
+ }
+ assignPath = path.substr(options.path.length + 1);
+ }
+ const parts = assignPath.split('.');
+ v = clone(self.get(path), options);
+ if (v === void 0) {
+ continue;
+ }
+ const plen = parts.length;
+ cur = json;
+ for (let j = 0; j < plen - 1; ++j) {
+ cur[parts[j]] = cur[parts[j]] || {};
+ cur = cur[parts[j]];
+ }
+ cur[parts[plen - 1]] = v;
+ }
+
+ return json;
+}
+
+/*!
+ * Applies virtuals properties to `json`.
+ *
+ * @param {Document} self
+ * @param {Object} json
+ * @return {Object} `json`
+ */
+
+function applyGetters(self, json, options) {
+ const schema = self.schema;
+ const paths = Object.keys(schema.paths);
+ let i = paths.length;
+ let path;
+ let cur = self._doc;
+ let v;
+
+ if (!cur) {
+ return json;
+ }
+
+ while (i--) {
+ path = paths[i];
+
+ const parts = path.split('.');
+ const plen = parts.length;
+ const last = plen - 1;
+ let branch = json;
+ let part;
+ cur = self._doc;
+
+ if (!self.isSelected(path)) {
+ continue;
+ }
+
+ for (let ii = 0; ii < plen; ++ii) {
+ part = parts[ii];
+ v = cur[part];
+ if (ii === last) {
+ const val = self.get(path);
+ branch[part] = clone(val, options);
+ } else if (v == null) {
+ if (part in cur) {
+ branch[part] = v;
+ }
+ break;
+ } else {
+ branch = branch[part] || (branch[part] = {});
+ }
+ cur = v;
+ }
+ }
+
+ return json;
+}
+
+/*!
+ * Applies schema type transforms to `json`.
+ *
+ * @param {Document} self
+ * @param {Object} json
+ * @return {Object} `json`
+ */
+
+function applySchemaTypeTransforms(self, json) {
+ const schema = self.schema;
+ const paths = Object.keys(schema.paths || {});
+ const cur = self._doc;
+
+ if (!cur) {
+ return json;
+ }
+
+ for (const path of paths) {
+ const schematype = schema.paths[path];
+ if (typeof schematype.options.transform === 'function') {
+ const val = self.get(path);
+ const transformedValue = schematype.options.transform.call(self, val);
+ throwErrorIfPromise(path, transformedValue);
+ json[path] = transformedValue;
+ } else if (schematype.$embeddedSchemaType != null &&
+ typeof schematype.$embeddedSchemaType.options.transform === 'function') {
+ const vals = [].concat(self.get(path));
+ const transform = schematype.$embeddedSchemaType.options.transform;
+ for (let i = 0; i < vals.length; ++i) {
+ const transformedValue = transform.call(self, vals[i]);
+ vals[i] = transformedValue;
+ throwErrorIfPromise(path, transformedValue);
+ }
+
+ json[path] = vals;
+ }
+ }
+
+ return json;
+}
+
+function throwErrorIfPromise(path, transformedValue) {
+ if (isPromise(transformedValue)) {
+ throw new Error('`transform` function must be synchronous, but the transform on path `' + path + '` returned a promise.');
+ }
+}
+
+/*!
+ * ignore
+ */
+
+function omitDeselectedFields(self, json) {
+ const schema = self.schema;
+ const paths = Object.keys(schema.paths || {});
+ const cur = self._doc;
+
+ if (!cur) {
+ return json;
+ }
+
+ let selected = self.$__.selected;
+ if (selected === void 0) {
+ selected = {};
+ queryhelpers.applyPaths(selected, schema);
+ }
+ if (selected == null || Object.keys(selected).length === 0) {
+ return json;
+ }
+
+ for (const path of paths) {
+ if (selected[path] != null && !selected[path]) {
+ delete json[path];
+ }
+ }
+
+ return json;
+}
+
+/**
+ * The return value of this method is used in calls to JSON.stringify(doc).
+ *
+ * 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.
+ *
+ * schema.set('toJSON', { virtuals: true })
+ *
+ * See [schema options](/docs/guide.html#toJSON) for details.
+ *
+ * @param {Object} options
+ * @return {Object}
+ * @see Document#toObject #document_Document-toObject
+ * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.toJSON = function(options) {
+ return this.$toObject(options, true);
+};
+
+/**
+ * If this document is a subdocument or populated document, returns the document's
+ * parent. Returns `undefined` otherwise.
+ *
+ * @api public
+ * @method parent
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.parent = function() {
+ return this.$__.parent;
+};
+
+/**
+ * Helper for console.log
+ *
+ * @api public
+ * @method inspect
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.inspect = function(options) {
+ const isPOJO = utils.isPOJO(options);
+ let opts;
+ if (isPOJO) {
+ opts = options;
+ opts.minimize = false;
+ }
+ const ret = this.toObject(opts);
+
+ if (ret == null) {
+ // If `toObject()` returns null, `this` is still an object, so if `inspect()`
+ // prints out null this can cause some serious confusion. See gh-7942.
+ return 'MongooseDocument { ' + ret + ' }';
+ }
+
+ return ret;
+};
+
+if (inspect.custom) {
+ /*!
+ * Avoid Node deprecation warning DEP0079
+ */
+
+ Document.prototype[inspect.custom] = Document.prototype.inspect;
+}
+
+/**
+ * Helper for console.log
+ *
+ * @api public
+ * @method toString
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.toString = function() {
+ const ret = this.inspect();
+ if (typeof ret === 'string') {
+ return ret;
+ }
+ return inspect(ret);
+};
+
+/**
+ * Returns true if the Document stores the same data as doc.
+ *
+ * Documents are considered equal when they have matching `_id`s, unless neither
+ * document has an `_id`, in which case this function falls back to using
+ * `deepEqual()`.
+ *
+ * @param {Document} doc a document to compare
+ * @return {Boolean}
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.equals = function(doc) {
+ if (!doc) {
+ return false;
+ }
+
+ const tid = this.$__getValue('_id');
+ const docid = doc.$__ != null ? doc.$__getValue('_id') : doc;
+ if (!tid && !docid) {
+ return deepEqual(this, doc);
+ }
+ return tid && tid.equals
+ ? tid.equals(docid)
+ : tid === docid;
+};
+
+/**
+ * Populates document references, executing the `callback` when complete.
+ * If you want to use promises instead, use this function with
+ * [`execPopulate()`](#document_Document-execPopulate)
+ *
+ * ####Example:
+ *
+ * doc
+ * .populate('company')
+ * .populate({
+ * path: 'notes',
+ * match: /airline/,
+ * select: 'text',
+ * model: 'modelName'
+ * options: opts
+ * }, function (err, user) {
+ * assert(doc._id === user._id) // the document itself is passed
+ * })
+ *
+ * // summary
+ * doc.populate(path) // not executed
+ * doc.populate(options); // not executed
+ * doc.populate(path, callback) // executed
+ * doc.populate(options, callback); // executed
+ * doc.populate(callback); // executed
+ * doc.populate(options).execPopulate() // executed, returns promise
+ *
+ *
+ * ####NOTE:
+ *
+ * Population does not occur unless a `callback` is passed *or* you explicitly
+ * call `execPopulate()`.
+ * Passing the same path a second time will overwrite the previous path options.
+ * See [Model.populate()](#model_Model.populate) for explaination of options.
+ *
+ * @see Model.populate #model_Model.populate
+ * @see Document.execPopulate #document_Document-execPopulate
+ * @param {String|Object} [path] The path to populate or an options object
+ * @param {Function} [callback] When passed, population is invoked
+ * @api public
+ * @return {Document} this
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.populate = function populate() {
+ if (arguments.length === 0) {
+ return this;
+ }
+
+ const pop = this.$__.populate || (this.$__.populate = {});
+ const args = utils.args(arguments);
+ let fn;
+
+ if (typeof args[args.length - 1] === 'function') {
+ fn = args.pop();
+ }
+
+ // allow `doc.populate(callback)`
+ if (args.length) {
+ // use hash to remove duplicate paths
+ const res = utils.populate.apply(null, args);
+ for (const populateOptions of res) {
+ pop[populateOptions.path] = populateOptions;
+ }
+ }
+
+ if (fn) {
+ const paths = utils.object.vals(pop);
+ this.$__.populate = undefined;
+ let topLevelModel = this.constructor;
+ if (this.$__isNested) {
+ topLevelModel = this.$__[scopeSymbol].constructor;
+ const nestedPath = this.$__.nestedPath;
+ paths.forEach(function(populateOptions) {
+ populateOptions.path = nestedPath + '.' + populateOptions.path;
+ });
+ }
+
+ // Use `$session()` by default if the document has an associated session
+ // See gh-6754
+ if (this.$session() != null) {
+ const session = this.$session();
+ paths.forEach(path => {
+ if (path.options == null) {
+ path.options = { session: session };
+ return;
+ }
+ if (!('session' in path.options)) {
+ path.options.session = session;
+ }
+ });
+ }
+
+ topLevelModel.populate(this, paths, fn);
+ }
+
+ return this;
+};
+
+/**
+ * Explicitly executes population and returns a promise. Useful for ES2015
+ * integration.
+ *
+ * ####Example:
+ *
+ * const promise = doc.
+ * populate('company').
+ * populate({
+ * path: 'notes',
+ * match: /airline/,
+ * select: 'text',
+ * model: 'modelName'
+ * options: opts
+ * }).
+ * execPopulate();
+ *
+ * // summary
+ * doc.execPopulate().then(resolve, reject);
+ *
+ * // you can also use doc.execPopulate(options) as a shorthand for
+ * // doc.populate(options).execPopulate()
+ *
+ *
+ * ####Example:
+ * const promise = doc.execPopulate({ path: 'company', select: 'employees' });
+ *
+ * // summary
+ * promise.then(resolve,reject);
+ *
+ * @see Document.populate #document_Document-populate
+ * @api public
+ * @param {Function} [callback] optional callback. If specified, a promise will **not** be returned
+ * @return {Promise} promise that resolves to the document when population is done
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.execPopulate = function(callback) {
+ const isUsingShorthand = callback != null && typeof callback !== 'function';
+ if (isUsingShorthand) {
+ return this.populate.apply(this, arguments).execPopulate();
+ }
+
+ return promiseOrCallback(callback, cb => {
+ this.populate(cb);
+ }, this.constructor.events);
+};
+
+/**
+ * Gets _id(s) used during population of the given `path`.
+ *
+ * ####Example:
+ *
+ * Model.findOne().populate('author').exec(function (err, doc) {
+ * console.log(doc.author.name) // Dr.Seuss
+ * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
+ * })
+ *
+ * If the path was not populated, undefined is returned.
+ *
+ * @param {String} path
+ * @return {Array|ObjectId|Number|Buffer|String|undefined}
+ * @memberOf Document
+ * @instance
+ * @api public
+ */
+
+Document.prototype.populated = function(path, val, options) {
+ // val and options are internal
+ if (val === null || val === void 0) {
+ if (!this.$__.populated) {
+ return undefined;
+ }
+ const v = this.$__.populated[path];
+ if (v) {
+ return v.value;
+ }
+ return undefined;
+ }
+
+ // internal
+ if (val === true) {
+ if (!this.$__.populated) {
+ return undefined;
+ }
+ return this.$__.populated[path];
+ }
+
+ this.$__.populated || (this.$__.populated = {});
+ this.$__.populated[path] = { value: val, options: options };
+
+ // If this was a nested populate, make sure each populated doc knows
+ // about its populated children (gh-7685)
+ const pieces = path.split('.');
+ for (let i = 0; i < pieces.length - 1; ++i) {
+ const subpath = pieces.slice(0, i + 1).join('.');
+ const subdoc = this.get(subpath);
+ if (subdoc != null && subdoc.$__ != null && this.populated(subpath)) {
+ const rest = pieces.slice(i + 1).join('.');
+ subdoc.populated(rest, val, options);
+ // No need to continue because the above recursion should take care of
+ // marking the rest of the docs as populated
+ break;
+ }
+ }
+
+ return val;
+};
+
+/**
+ * Takes a populated field and returns it to its unpopulated state.
+ *
+ * ####Example:
+ *
+ * Model.findOne().populate('author').exec(function (err, doc) {
+ * console.log(doc.author.name); // Dr.Seuss
+ * console.log(doc.depopulate('author'));
+ * console.log(doc.author); // '5144cf8050f071d979c118a7'
+ * })
+ *
+ * If the path was not populated, this is a no-op.
+ *
+ * @param {String} path
+ * @return {Document} this
+ * @see Document.populate #document_Document-populate
+ * @api public
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.depopulate = function(path) {
+ if (typeof path === 'string') {
+ path = path.split(' ');
+ }
+
+ let populatedIds;
+ const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
+ const populated = get(this, '$__.populated', {});
+
+ if (arguments.length === 0) {
+ // Depopulate all
+ for (const virtualKey of virtualKeys) {
+ delete this.$$populatedVirtuals[virtualKey];
+ delete this._doc[virtualKey];
+ delete populated[virtualKey];
+ }
+
+ const keys = Object.keys(populated);
+
+ for (const key of keys) {
+ populatedIds = this.populated(key);
+ if (!populatedIds) {
+ continue;
+ }
+ delete populated[key];
+ this.$set(key, populatedIds);
+ }
+ return this;
+ }
+
+ for (const singlePath of path) {
+ populatedIds = this.populated(singlePath);
+ delete populated[singlePath];
+
+ if (virtualKeys.indexOf(singlePath) !== -1) {
+ delete this.$$populatedVirtuals[singlePath];
+ delete this._doc[singlePath];
+ } else if (populatedIds) {
+ this.$set(singlePath, populatedIds);
+ }
+ }
+ return this;
+};
+
+
+/**
+ * Returns the full path to this document.
+ *
+ * @param {String} [path]
+ * @return {String}
+ * @api private
+ * @method $__fullPath
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.$__fullPath = function(path) {
+ // overridden in SubDocuments
+ return path || '';
+};
+
+/**
+ * Returns the changes that happened to the document
+ * in the format that will be sent to MongoDB.
+ *
+ * #### Example:
+ *
+ * const userSchema = new Schema({
+ * name: String,
+ * age: Number,
+ * country: String
+ * });
+ * const User = mongoose.model('User', userSchema);
+ * const user = await User.create({
+ * name: 'Hafez',
+ * age: 25,
+ * country: 'Egypt'
+ * });
+ *
+ * // returns an empty object, no changes happened yet
+ * user.getChanges(); // { }
+ *
+ * user.country = undefined;
+ * user.age = 26;
+ *
+ * user.getChanges(); // { $set: { age: 26 }, { $unset: { country: 1 } } }
+ *
+ * await user.save();
+ *
+ * user.getChanges(); // { }
+ *
+ * Modifying the object that `getChanges()` returns does not affect the document's
+ * change tracking state. Even if you `delete user.getChanges().$set`, Mongoose
+ * will still send a `$set` to the server.
+ *
+ * @return {Object}
+ * @api public
+ * @method getChanges
+ * @memberOf Document
+ * @instance
+ */
+
+Document.prototype.getChanges = function() {
+ const delta = this.$__delta();
+
+ const changes = delta ? delta[1] : {};
+ return changes;
+};
+
+/*!
+ * Module exports.
+ */
+
+Document.ValidationError = ValidationError;
+module.exports = exports = Document;