From 81ddf9b700bc48a1f8e472209f080f9c1d9a9b09 Mon Sep 17 00:00:00 2001 From: Piotr Russ Date: Wed, 18 Nov 2020 23:26:45 +0100 Subject: rm node_modules --- node_modules/mongoose/lib/document.js | 4056 --------------------------------- 1 file changed, 4056 deletions(-) delete mode 100644 node_modules/mongoose/lib/document.js (limited to 'node_modules/mongoose/lib/document.js') diff --git a/node_modules/mongoose/lib/document.js b/node_modules/mongoose/lib/document.js deleted file mode 100644 index 9e8cb9a..0000000 --- a/node_modules/mongoose/lib/document.js +++ /dev/null @@ -1,4056 +0,0 @@ -'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; -- cgit v1.2.3