


















Denza's flagship full-size luxury liftback with up to 1036km EV range, 850kW triple-motor performance, and plug-in hybrid options with 401km electric range.
Back to Denza Z9 GT5.8s
0-100 km/h
503 hp
(370 kW)
122.5 kWh
Battery Capacity
0.15h
30-80%
5 seats
Seating
Rear-engine rear-wheel drive
Drivetrain
| Range Cltc | 1036 |
| Max Power Kw | 370 |
| Max Power Hp | 503 |
| Battery Kwh | 122.5 |
| Acceleration 0 100 | 5.8 |
| Top Speed | 240 |
| Drivetrain | Rear-engine rear-wheel drive |
| $IsNew | ✕ |
| Manufacturer | 腾势汽车 |
| Vehicle Level | 中大型车 |
| Energy Type | Pure electric |
| Launch Date | 2026-03-05 |
| CLTC Pure Electric Range (km) | 1036 |
| Fast Charging Time (hours) | 0.15 |
| Fast Charging Range (%) | 10-97 |
| Maximum Power (kW) | 370 |
| Maximum Torque (N·m) | 500 |
| Transmission | Electric vehicle single-speed transmission |
| Body Structure | 5-door 5-seat liftback |
| Electric Motor (Ps) | 503 |
| Length×Width×Height (mm) | 5180*1990*1490 |
| Official 0-100 km/h Acceleration (s) | 5.8 |
| Maximum Speed (km/h) | 240 |
| Electric Energy Equivalent Fuel Consumption | 1.54 |
| Vehicle Warranty | Six years or 150,000 km |
| Curb Weight (kg) | 2700 |
| Maximum Full Load Mass (kg) | 3075 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | basicParameters |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Length (mm) | 5180 |
| Width (mm) | 1990 |
| Height (mm) | 1490 |
| Wheelbase (mm) | 3125 |
| Front Track Width (mm) | 1710 |
| Rear Track Width (mm) | 1715 |
| Approach Angle (°) | 13 |
| Departure Angle (°) | 16 |
| Minimum Turning Radius (m) | 5.4 |
| Body Structure | Liftback |
| Door Opening Method | Swing door |
| Number of Doors | 5 |
| Number of Seats | 5 |
| Trunk Volume (L) | 495 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | body |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Energy Type | Pure electric |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | engine |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Motor Type | 永磁 |
| Total Motor Power (kW) | 370 |
| Total Motor Horsepower (Ps) | 503 |
| Total Motor Torque (N·m) | 500 |
| Rear Motor Brand | 比亚迪 |
| Rear Motor Model | TZ226QYD |
| Rear Motor Maximum Power (kW) | 370 |
| Rear Motor Maximum Torque (N·m) | 500 |
| Number of Drive Motors | Single motor |
| Motor Layout | Rear |
| Three-Electric First Owner Warranty Policy | Lifetime warranty/non-commercial (exclusion clause subject to official terms) |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | electricMotor |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Battery Type | Lithium iron phosphate battery |
| Battery Cell Brand | FinDreams |
| Battery Specific Technology | 第二代刀片电池 |
| Battery Cooling Method | 直冷 |
| CLTC Pure Electric Range (km) | 1036 |
| Battery Energy (kWh) | 122.5 |
| Power Consumption per 100km (kWh) | 13.3 |
| Fast Charging Function | Supported |
| Fast Charging Time (hours) | 0.15 |
| Fast Charging Range (%) | 10-97 |
| Slow Charging Port Location | Right rear side |
| Fast Charging Port Location | Right rear side |
| External AC Discharge Power (kW) | 6 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | batteryCharging |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| Capacity Kwh | 122.5 |
| Charging Time 30 80 | 0.15h (10-97) |
| Charging Time 30 80 | 0.15h |
| Seats | 5 |
| Length (mm) | 5180 |
| Width (mm) | 1990 |
| Height (mm) | 1490 |
| Wheelbase (mm) | 3125 |
| $IsNew | ✕ |
| Abbreviation | Electric vehicle single-speed transmission |
| Number of Gears | 1 |
| Transmission Type | Fixed gear ratio transmission |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | gearbox |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Drive Mode | Rear-engine rear-wheel drive |
| Front Suspension Type | Double wishbone independent suspension |
| Rear Suspension Type | Five-link independent suspension |
| Power Steering Type | Electric power steering |
| Body Structure | Unibody |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | chassisSteering |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Front Brake Type | Ventilated disc |
| Rear Brake Type | Ventilated disc |
| Parking Brake Type | Electronic parking |
| Front Tire Specifications | 255/45 R20 |
| Rear Tire Specifications | 255/45 R20 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | wheelBrakes |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Driver/Passenger Seat Airbags | Driver +/Passenger + |
| Front/Rear Side Airbags | Front +/Rear + |
| Front/Rear Head Airbags (Curtain) | Front +/Rear + |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | passiveSafety |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Tire Pressure Monitoring | Tire pressure display |
| Seat Belt Unfastened Reminder | Full vehicle |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | activeSafety |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Driving Mode Switch | Snow |
| Variable Suspension Function | 悬架高低调节 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | drivingControl |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Front/Rear Parking Radar | Front +/Rear + |
| Driving Assistance Images | 360-degree panoramic camera |
| Forward Perception Camera | 双目 |
| Number of Cameras | 11 |
| Number of In-Car Cameras | 1 |
| Number of Ultrasonic Radars | 12 |
| Number of Millimeter Wave Radars | 3 |
| Number of LiDARs | 1 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | drivingHardware |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Cruise Control System | Full-speed adaptive cruise |
| Assisted Driving System | DiPilot |
| Driving Assistance Level | L2 |
| Map Brand | 高德 |
| Assisted Driving Sections | Highway sections |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | drivingFeatures |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Rim Material | Aluminum alloy |
| Key Type | NFC, RFID钥匙 |
| Keyless Entry Function | Full vehicle |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | appearanceAntiTheft |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Low Beam Light Source | LED |
| High Beam Light Source | LED |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | exteriorLights |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Skylight Type | 分段式不可开启天窗 |
| Front/Rear Electric Windows | Front +/Rear + |
| One-Touch Window Lift Function | Full vehicle |
| Side Window Multi-Layer Soundproof Glass | 后排 |
| Car Makeup Mirror | 主驾驶+照明灯 |
| Rain Sensor Wiper Function | Rain-sensing |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | skylightGlass |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Exterior Rearview Mirror Function | Mirror heating |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | exteriorRearviewMirror |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Central Control Color Screen | Touch LCD screen |
| Central Control Screen Size | 17.3英寸 |
| Central Control Screen Resolution | 2.5K |
| Mobile Phone Interconnection/Mapping | 原厂互联/映射 |
| Voice Recognition Control System | Air conditioning |
| Voice Assistant Wake-Up Word | Hi, Denza |
| Voice Wake-Up Recognition by Region | Four zones |
| In-Vehicle Intelligent System | DiLink |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | screenSystem |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| 4G/5G Network | 5G |
| Mobile App Remote Function | Charging management |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | intelligentConfiguration |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Steering Wheel Material | Leather |
| Steering Wheel Position Adjustment | Electric up/down + front/rear adjustment |
| Gear Shifting | Electronic column-mounted shifting |
| Trip Computer Display Screen | Color |
| LCD Instrument Size | 13.2英寸 |
| HUD Head-Up Size | 50英寸 |
| Interior Rearview Mirror Function | 流媒体 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | steeringWheelInteriorRearviewMirror |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Multimedia/Charging Port | Type-C |
| USB/Type-C Ports Quantity | 前排2个/后排2个 |
| Mobile Phone Wireless Charging Function | 后排 |
| Mobile Phone Wireless Charging Power (W) | 50 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | inCarCharging |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Seat Material | Genuine leather |
| Main Seat Adjustment Method | 腿托调节 |
| Passenger Seat Adjustment Method | 腿托调节 |
| Driver/Passenger Seat Electric Adjustment | Driver +/Passenger + |
| Front Seat Functions | 头枕扬声器(仅驾驶位) |
| Electric Seat Memory Function | 副驾驶位 |
| Second Row Seat Adjustment | 靠背调节 |
| Second Row Seat Function | Ventilation |
| Rear Seat Folding | Proportional folding |
| Front/Rear Center Armrest | Front +/Rear + |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | seatConfiguration |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Speaker Brand Name | DEVIALET帝瓦雷 |
| Number of Speakers | 24喇叭 |
| Interior Ambient Lighting | 128色 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | audioInteriorLighting |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Air Conditioning Temperature Control Method | Automatic air conditioning |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | airConditioningRefrigerator |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
| $IsNew | ✕ |
| Exterior Colors | 冬日青 |
| Interior Colors | ¥10000 |
| function Object() { [native code] } | function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); } |
| ToBSON | function() { return this.toObject(internalToObjectOptions); } |
| $BasePath | colors |
| $ Init | function syncWrapper() { const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); const toReturn = fn.apply(this, modifiedArgs); const result = _this.execPostSync(name, this, [toReturn]); return result[0]; } |
| Save | async function save(options) { options = options || {}; if (!options.suppressWarning) { utils.warn('mongoose: calling `save()` on a subdoc does **not** save ' + 'the document to MongoDB, it only runs save middleware. ' + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + 'if you\'re sure this behavior is right for your app.'); } return await this.$__save(); } |
| $ FullPath | function(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; } |
| $ PathRelativeToParent | function(p) { // If this subdocument has a stored relative path (set by map when subdoc is created), // use it directly to avoid string operations if (this.$pathRelativeToParent != null) { return p == null ? this.$pathRelativeToParent : this.$pathRelativeToParent + '.' + p; } if (p == null) { return this.$basePath; } if (!this.$basePath) { return p; } return [this.$basePath, p].join('.'); } |
| $ Save | async function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); } |
| $IsValid | function(path) { const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { return parent.$isValid(fullPath); } return Document.prototype.$isValid.call(this, path); } |
| MarkModified | function(path) { Document.prototype.markModified.call(this, path); const parent = this.$parent(); if (parent == null) { return; } const pathToMark = this.$__pathRelativeToParent(path); if (pathToMark == null) { return; } const myPath = this.$__pathRelativeToParent().replace(/\.$/, ''); if (parent.isDirectModified(myPath) || this.isNew) { return; } this.$__parent.markModified(pathToMark, this); } |
| IsModified | function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { paths = (Array.isArray(paths) ? paths : paths.split(' ')); paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null); } else if (!paths) { paths = this.$__pathRelativeToParent(); } return parent.$isModified(paths, options, modifiedPaths); } return Document.prototype.isModified.call(this, paths, options, modifiedPaths); } |
| $MarkValid | function(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } } |
| Invalidate | function(path, err, val) { Document.prototype.invalidate.call(this, path, err, val); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.invalidate(fullPath, err, val); } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) { throw err; } return this.ownerDocument().$__.validationError; } |
| $Ignore | function(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } } |
| OwnerDocument | function() { if (this.$__.ownerDocument) { return this.$__.ownerDocument; } let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, true)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } this.$__.fullPath = paths.join('.'); this.$__.ownerDocument = parent; return this.$__.ownerDocument; } |
| $ FullPathWithIndexes | function() { let parent = this; const paths = []; const seenDocs = new Set([parent]); while (true) { if (typeof parent.$__pathRelativeToParent !== 'function') { break; } paths.unshift(parent.$__pathRelativeToParent(void 0, false)); const _parent = parent.$parent(); if (_parent == null) { break; } parent = _parent; if (seenDocs.has(parent)) { throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself'); } seenDocs.add(parent); } return paths.join('.'); } |
| Parent | function() { return this.$__parent; } |
| $Parent | function() { return this.$__parent; } |
| $ SetParent | function $__setParent(parent) { this.$__parent = parent; } |
| $ RemoveFromParent | function() { this.$__parent.set(this.$basePath, null); } |
| DeleteOne | function deleteOne(options) { registerRemoveListener(this); // If removing entire doc, no need to remove subdoc if (!options?.noop) { this.$__removeFromParent(); const owner = this.ownerDocument(); owner.$__.removedSubdocs = owner.$__.removedSubdocs || []; owner.$__.removedSubdocs.push(this); } } |
| Populate | function() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); } |
| Inspect | function() { return this.toObject(); } |
| $ToObject | function $toObject(options, json) { const ret = Document.prototype.$toObject.call(this, options, json); // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; } } return ret; } |
| $IsMongooseDocumentPrototype | ✓ |
| On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $On | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Once | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Emit | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $Listeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $SetMaxListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $RemoveAllListeners | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $AddListener | function() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); } |
| $ 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 = this.$__schema.paths[p].splitPath(); 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]; if (!curPath.length) { curPath = piece; } else { curPath += '.' + 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; } |
| Init | function(doc, opts, fn) { if (typeof opts === 'function') { fn = opts; opts = null; } if (doc == null) { throw new ObjectParameterError(doc, 'doc', 'init'); } this.$__init(doc, opts); if (fn) { fn(null, this); } return this; } |
| $Init | function() { return this.constructor.prototype.init.apply(this, arguments); } |
| UpdateOne | function updateOne(update, options) { const query = this.constructor.updateOne(); const self = this; query.pre(async function queryPreUpdateOne() { const res = await self._execDocumentPreHooks('updateOne', self, update, options); // `self` is passed to pre hooks as argument for backwards compatibility, but that // isn't the actual arguments passed to the wrapped function. if (res[0] !== self || res[1] !== update || res[2] !== options) { throw new Error('Document updateOne pre hooks cannot overwrite arguments'); } query.updateOne({ _id: self._doc._id }, update, options); // Apply custom where conditions _after_ document updateOne middleware for // consistency with save() - sharding plugin needs to set $where if (self.$where != null) { this.where(self.$where); } if (self.$session() != null) { if (!('session' in query.options)) { query.options.session = self.$session(); } } return res; }); query.post(function queryPostUpdateOne() { return self._execDocumentPostHooks('updateOne'); }); return query; } |
| ReplaceOne | function replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); } |
| $Session | function $session(session) { if (arguments.length === 0) { if (this.$__.session?.hasEnded) { this.$__.session = null; return null; } return this.$__.session; } if (session?.hasEnded) { throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' + 'called `endSession()` on the session you are passing to `$session()`.'); } if (session == null && this.$__.session == null) { return; } this.$__.session = session; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$session(session); } } return session; } |
| $Timestamps | function $timestamps(value) { if (arguments.length === 0) { if (this.$__.timestamps != null) { return this.$__.timestamps; } if (this.$__schema) { return this.$__schema.options.timestamps; } return undefined; } const currentValue = this.$timestamps(); if (value !== currentValue) { this.$__.timestamps = value; } return this; } |
| Overwrite | function overwrite(obj) { const keys = new Set(Object.keys(this._doc)); for (const key of Object.keys(obj)) { keys.add(key); } const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); } return this; } |
| $Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| Set | function $set(path, val, type, options) { if (utils.isPOJO(type)) { options = type; type = undefined; } 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 userSpecifiedStrict = options && 'strict' in options; let strict = userSpecifiedStrict ? options.strict : this.$__.strictMode; if (adhoc) { adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options); } if (path == null) { [path, val] = [val, path]; } else if (typeof path !== 'string') { // new Document({ key: val }) if (path instanceof Document) { if (path.$__isNested) { path = path.toObject(); } else { // This ternary is to support gh-7898 (copying virtuals if same schema) // while not breaking gh-10819, which for some reason breaks if we use toObject() path = path.$__schema === this.$__schema ? applyVirtuals(path, { ...path._doc }) : path._doc; } } if (path == null) { [path, val] = [val, path]; } prefix = val ? val + '.' : ''; keys = getKeysInSchemaOrder(this.$__schema, path); const len = keys.length; // `_skipMinimizeTopLevel` is because we may have deleted the top-level // nested key to ensure key order. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false; if (len === 0 && _skipMinimizeTopLevel) { delete options._skipMinimizeTopLevel; if (val) { this.$set(val, {}); } return this; } options = Object.assign({}, options, { _skipMinimizeTopLevel: false }); for (let i = 0; i < len; ++i) { key = keys[i]; const pathName = prefix ? prefix + key : key; pathtype = this.$__schema.pathType(pathName); const valForKey = path[key]; // 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 && valForKey != null && pathtype === 'nested' && this._doc[key] != null) { delete this._doc[key]; } if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') { this.$set(pathName, valForKey, constructing, options); $applyDefaultsToNested(this.$get(pathName), pathName, this); continue; } else if (strict) { // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) if (constructing && valForKey === void 0 && this.$get(pathName) !== void 0) { continue; } if (pathtype === 'adhocOrUndefined') { pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); } if (pathtype === 'real' || pathtype === 'virtual') { this.$set(pathName, valForKey, constructing, options); } else if (pathtype === 'nested' && valForKey instanceof Document) { this.$set(pathName, valForKey.toObject({ transform: false }), constructing, options); } else if (strict === 'throw') { if (pathtype === 'nested') { throw new ObjectExpectedError(key, valForKey); } else { throw new StrictModeError(key); } } else if (pathtype === 'nested' && valForKey == null) { this.$set(pathName, valForKey, constructing, options); } } else { this.$set(pathName, valForKey, constructing, options); } } // Ensure all properties are in correct order const orderedDoc = {}; const orderedKeys = Object.keys(this.$__schema.tree); for (let i = 0, len = orderedKeys.length; i < len; ++i) { (key = orderedKeys[i]) && (Object.hasOwn(this._doc, key)) && (orderedDoc[key] = undefined); } this._doc = Object.assign(orderedDoc, this._doc); return this; } let pathType = this.$__schema.pathType(path); let parts = null; if (pathType === 'adhocOrUndefined') { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true }); } if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) { // May be path underneath non-strict schema if (parts == null) { parts = path.indexOf('.') === -1 ? [path] : path.split('.'); } const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts); if (subdocStrict !== undefined) { strict = subdocStrict; } } // Assume this is a Mongoose document that was copied into a POJO using // `Object.assign()` or `{...doc}` val = handleSpreadDoc(val, true); // if this doc is being constructed we should not trigger getters const priorVal = (() => { if (this.$__.priorDoc != null) { return this.$__.priorDoc.$__getValue(path); } if (constructing) { return void 0; } return this.$__getValue(path); })(); if (pathType === 'nested' && val) { if (typeof val === 'object' && val != null) { if (val.$__ != null) { val = val.toObject(internalToObjectOptions); } if (val == null) { this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } const wasModified = this.$isModified(path); const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path); if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) { const initialVal = this.$__getValue(path); this.$__.savedState[path] = initialVal; const keys = Object.keys(initialVal || {}); for (const key of keys) { this.$__.savedState[path + '.' + key] = initialVal[key]; } } if (!merge) { this.$__setValue(path, null); cleanModifiedSubpaths(this, path); } else { return this.$set(val, path, constructing, options); } const keys = getKeysInSchemaOrder(this.$__schema, val, path); this.$__setValue(path, {}); for (const key of keys) { this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true }); } if (priorVal != null && (!wasModified || hasInitialVal) && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) { this.unmarkModified(path); } else { this.markModified(path); } return this; } this.invalidate(path, new MongooseError.CastError('Object', val, path)); return this; } let schema; if (parts == null) { 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; } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) { // Map of mixed and not the last element in the path resolves to mixed mixed = true; schema = schema.$__schemaType; 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 instanceof Map ? cur.get(parts[i]) : 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 `MongoServerError: 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 { const len = parts.length; for (i = 0; i < len; ++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 (!schema) { this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (pathType === 'nested' && val == null) { cleanModifiedSubpaths(this, path); } 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 (val != null && merge && schema.$isSingleNested) { 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 refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref; const ref = refOpt?.modelName || refOpt; 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 && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) { const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id; this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor }); val.$__.wasPopulated = { value: unpopulatedValue }; didPopulate = true; } let popOpts; const typeKey = this.$__schema.options.typeKey; if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts); for (const doc of val) { doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } if (!refMatches || !schema.$isSingleNested || !val.$__) { // 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. let setterContext = this; if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) { setterContext = getDeepestSubdocumentForPath(this, parts, this.schema); } if (options?.overwriteImmutable) { val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true }); } else { val = schema.applySetters(val, setterContext, false, priorVal, { path }); } } if (Array.isArray(val) && !Array.isArray(schema) && schema.$isMongooseDocumentArray && val.length !== 0 && 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.set(i, val[i]._doc._id, true); } } } delete this.$__.populated[path]; } if (val != null && schema.$isSingleNested) { _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) { let savedState = null; let savedStatePath = null; if (!constructing) { const doc = this.$isSubdocument ? this.ownerDocument() : this; savedState = doc.$__.savedState; savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path; doc.$__saveInitialState(savedStatePath); } this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal); const isInTransaction = !!this.$__.session?.transaction; const isModifiedWithinTransaction = this.$__.session && this.$__.session[sessionNewDocuments] && this.$__.session[sessionNewDocuments].has(this) && this.$__.session[sessionNewDocuments].get(this).modifiedPaths && !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath); if (savedState != null && Object.hasOwn(savedState, savedStatePath) && (!isInTransaction || isModifiedWithinTransaction) && utils.deepEqual(val, savedState[savedStatePath])) { this.unmarkModified(path); } } if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { cleanModifiedSubpaths(this, path); } else if (schema.$isSchemaMap && val == null) { cleanModifiedSubpaths(this, path); } return this; } |
| $ ShouldModify | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { if (options?._skipMarkModified) { return false; } if (this.$isNew) { return true; } // Is path already modified? If so, always modify. We may unmark modified later. if (path in this.$__.activePaths.getStatePaths('modify')) { return true; } 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.getStatePaths('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._doc._id, priorVal)) { return false; } if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) { return true; } if (!constructing && val !== null && val !== undefined && path in this.$__.activePaths.getStatePaths('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; } |
| $ Set | function(pathToMark, path, options, constructing, parts, schema, val, priorVal) { Embedded = Embedded || require('./types/arraySubdocument'); const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts, schema, val, priorVal); if (shouldModify) { if (this.$__.primitiveAtomics?.[path]) { delete this.$__.primitiveAtomics[path]; if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } this.markModified(pathToMark); // handle directly setting arrays (gh-1126) MongooseArray || (MongooseArray = require('./types/array')); if (val && utils.isMongooseArray(val)) { val._registerAtomic('$set', val); // Update embedded document parent references (gh-5189) if (utils.isMongooseDocumentArray(val)) { val.forEach(function(item) { item && item.__parentArray && (item.__parentArray = val); }); } } } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) { val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol]; val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol]; if (utils.isMongooseDocumentArray(val)) { val.forEach(doc => { if (doc != null) { doc.$isNew = false; } }); } } 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])) { continue; } if (last) { if (obj instanceof Map) { obj.set(parts[i], val); } else if (obj.$isSingleNested) { if (!(parts[i] in obj)) { obj[parts[i]] = val; obj._doc[parts[i]] = val; } else { obj._doc[parts[i]] = val; } if (shouldModify) { obj.markModified(parts[i]); } } else { obj[parts[i]] = val; } } else { const isMap = obj instanceof Map; let value = isMap ? obj.get(parts[i]) : obj[parts[i]]; if (utils.isPOJO(value)) { obj = value; } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { value = {}; if (isMap) { obj.set(parts[i], value); } else { obj[parts[i]] = value; } obj = value; } else { obj = value; } } } } |
| $ GetValue | function(path) { if (typeof path !== 'string' && !Array.isArray(path)) { throw new TypeError( `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})` ); } return utils.getValue(path, this._doc); } |
| $Inc | function $inc(path, val) { if (val == null) { val = 1; } if (Array.isArray(path)) { path.forEach((p) => this.$inc(p, val)); return this; } const schemaType = this.$__path(path); if (schemaType == null) { if (this.$__.strictMode === 'throw') { throw new StrictModeError(path); } else if (this.$__.strictMode === true) { return this; } } else if (schemaType.instance !== 'Number') { this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path)); return this; } const currentValue = this.$__getValue(path) || 0; let shouldSet = false; let valToSet = null; let valToInc = val; try { val = schemaType.cast(val); valToSet = schemaType.applySetters(currentValue + val, this); valToInc = valToSet - currentValue; shouldSet = true; } catch (err) { this.invalidate(path, new MongooseError.CastError('number', val, path, err)); } if (shouldSet) { this.$__.primitiveAtomics = this.$__.primitiveAtomics || {}; if (this.$__.primitiveAtomics[path] == null) { this.$__.primitiveAtomics[path] = { $inc: valToInc }; } else { this.$__.primitiveAtomics[path].$inc += valToInc; } this.markModified(path); this.$__setValue(path, valToSet); } return this; } |
| $ SetValue | function(path, val) { utils.setValue(path, val, this._doc); return this; } |
| Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $Get | function(path, type, options) { let adhoc; if (options == null) { options = {}; } if (type) { adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options); } const noDottedPath = options.noDottedPath; // Fast path if we know we're just accessing top-level path on the document: // just get the schema path, avoid `$__path()` because that does string manipulation let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path); if (schema == null) { schema = this.$__schema.virtualpath(path); if (schema != null) { return schema.applyGetters(void 0, this); } } if (noDottedPath) { let obj = this._doc[path]; if (adhoc) { obj = adhoc.cast(obj); } if (schema != null && options.getters !== false) { return schema.applyGetters(obj, this); } return obj; } if (schema?.instance === 'Mixed') { const virtual = this.$__schema.virtualpath(path); if (virtual != null) { schema = virtual; } } const hasDot = path.indexOf('.') !== -1; let obj = this._doc; const pieces = hasDot ? path.split('.') : [path]; // 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?._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, clone(obj) || {}, { path: path }); } return obj; } |
| $ Path | function(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); } |
| $ SaveInitialState | function $__saveInitialState(path) { const savedState = this.$__.savedState; const savedStatePath = path; if (savedState != null) { const firstDot = savedStatePath.indexOf('.'); const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot); if (!Object.hasOwn(savedState, topLevelPath)) { savedState[topLevelPath] = clone(this.$__getValue(topLevelPath)); } } } |
| UnmarkModified | function(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } } |
| DirectModifiedPaths | function() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); } |
| $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; } |
| ModifiedPaths | function(options) { options = options || {}; const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify')); const result = new Set(); let i = 0; let j = 0; const len = directModifiedPaths.length; for (i = 0; i < len; ++i) { const path = directModifiedPaths[i]; const parts = parentPaths(path); const pLen = parts.length; for (j = 0; j < pLen; ++j) { result.add(parts[j]); } if (!options.includeChildren) { continue; } let ii = 0; let cur = this.$get(path); if (typeof cur === 'object' && cur !== null) { if (cur._doc) { cur = cur._doc; } const len = cur.length; if (Array.isArray(cur)) { for (ii = 0; ii < len; ++ii) { const subPath = path + '.' + ii; if (!result.has(subPath)) { result.add(subPath); if (cur[ii] != null && cur[ii].$__) { const modified = cur[ii].modifiedPaths(); let iii = 0; const iiiLen = modified.length; for (iii = 0; iii < iiiLen; ++iii) { result.add(subPath + '.' + modified[iii]); } } } } } else { const keys = Object.keys(cur); let ii = 0; const len = keys.length; for (ii = 0; ii < len; ++ii) { result.add(path + '.' + keys[ii]); } } } } return Array.from(result); } |
| $IsModified | function(paths, options, modifiedPaths) { if (paths) { const ignoreAtomics = options?.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; } if (typeof paths === 'string') { paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' '); } for (const path of paths) { if (directModifiedPathsObj[path] != null) { return true; } } const modified = modifiedPaths || this[documentModifiedPaths](); const isModifiedChild = paths.some(function(path) { return !!~modified.indexOf(path); }); let directModifiedPaths = Object.keys(directModifiedPathsObj); if (ignoreAtomics) { directModifiedPaths = directModifiedPaths.filter(path => { const value = this.$__getValue(path); if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { return false; } return true; }); } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); }); }); } return this.$__.activePaths.some('modify'); } |
| $IsDefault | function(path) { if (path == null) { return this.$__.activePaths.some('default'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path)); } |
| $IsDeleted | function(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; } |
| IsDirectModified | function(path) { if (path == null) { return this.$__.activePaths.some('modify'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path); if (res || path.indexOf('.') === -1) { return res; } 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.isDirectModified(pieces.slice(i + 1).join('.'))) { return true; } } return false; } let paths = path; if (typeof paths === 'string') { paths = paths.split(' '); } return paths.some(path => this.isDirectModified(path)); } |
| IsInit | function(path) { if (path == null) { return this.$__.activePaths.some('init'); } if (typeof path === 'string' && path.indexOf(' ') === -1) { return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path); } let paths = path; if (!Array.isArray(paths)) { paths = paths.split(' '); } return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path)); } |
| IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| $ IsSelected | function isSelected(path) { if (this.$__.selected == null) { return true; } if (!path) { return false; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.$__isSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { 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; } const pathDot = path + '.'; for (const cur of paths) { if (cur === '_id') { continue; } if (cur.startsWith(pathDot)) { return inclusive || cur !== pathDot; } if (pathDot.startsWith(cur + '.')) { return inclusive; } } return !inclusive; } |
| IsDirectSelected | function isDirectSelected(path) { if (this.$__.selected == null) { return true; } if (path === '_id') { return this.$__.selected._id !== 0; } if (path.indexOf(' ') !== -1) { path = path.split(' '); } if (Array.isArray(path)) { return path.some(p => this.isDirectSelected(p)); } const paths = Object.keys(this.$__.selected); let inclusive = null; if (paths.length === 1 && paths[0] === '_id') { // only _id was selected. return this.$__.selected._id === 0; } for (const cur of paths) { if (cur === '_id') { continue; } if (!isDefiningProjection(this.$__.selected[cur])) { continue; } inclusive = !!this.$__.selected[cur]; break; } if (inclusive === null) { return true; } if (Object.hasOwn(this.$__.selected, path)) { return inclusive; } return !inclusive; } |
| Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| $Validate | async function validate(pathsToValidate, options) { if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') { throw new MongooseError('Document.prototype.validate() no longer accepts a callback'); } this.$op = 'validate'; if (arguments.length === 1) { if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } } if (options && typeof options.pathsToSkip === 'string') { const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1; options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' '); } const _skipParallelValidateCheck = options?._skipParallelValidateCheck; if (this.$isSubdocument != null) { // Skip parallel validate check for subdocuments } else if (this.$__.validating && !_skipParallelValidateCheck) { throw new ParallelValidateError(this); } else if (!_skipParallelValidateCheck) { this.$__.validating = true; } try { await this.$__validate(pathsToValidate, options); } finally { this.$op = null; this.$__.validating = null; } } |
| ExecDocumentPreHooks | async function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); } |
| ExecDocumentPostHooks | async function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); } |
| $ Validate | async function $__validate(pathsToValidate, options) { try { [options] = await this._execDocumentPreHooks('validate', options); } catch (error) { await this._execDocumentPostHooks('validate', error); return; } if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) { pathsToValidate = [...this.$__.saveOptions.pathsToSave]; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); const pathsToSkip = options?.pathsToSkip || null; let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) { throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`'); } } const _this = this; const _complete = () => { let validationError = this.$__.validationError; this.$__.validationError = null; this.$__.validating = null; 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 (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } this.$__.cachedRequired = {}; this.$emit('validate', _this); this.constructor.emit('validate', _this); 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 let paths; let doValidateOptionsByPath; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; doValidateOptionsByPath = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; doValidateOptionsByPath = pathDetails[1]; } if (typeof pathsToValidate === 'string') { pathsToValidate = pathsToValidate.split(' '); } if (paths.length === 0) { const error = _complete(); await this._execDocumentPostHooks('validate', error); return; } const validated = {}; let pathsToSave = this.$__.saveOptions?.pathsToSave; const promises = []; if (Array.isArray(pathsToSave)) { pathsToSave = new Set(pathsToSave); for (const path of paths) { if (!pathsToSave.has(path)) { continue; } promises.push(validatePath(path)); } } else { for (const path of paths) { promises.push(validatePath(path)); } } await Promise.all(promises); const error = _complete(); await this._execDocumentPostHooks('validate', error); async function validatePath(path) { if (path == null || validated[path]) { return; } validated[path] = true; const schemaType = _this.$__schema.path(path); if (!schemaType) { return; } // If user marked as invalid or there was a cast error, don't validate if (!_this.$isValid(path)) { return; } // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141) if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) { 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 ((pop = _this.$populated(path))) { val = pop; } else if (val?.$__?.wasPopulated) { // Array paths, like `somearray.1`, do not show up as populated with `$populated()`, // so in that case pull out the document's id val = val._doc._id; } const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ? _this.$__.pathsToScopes[path] : _this; const doValidateOptions = { ...doValidateOptionsByPath[path], path: path, validateAllPaths, _nestedValidate: true }; try { await schemaType.doValidate(val, scope, doValidateOptions); } catch (err) { const isSubdoc = schemaType.$isSingleNested || schemaType.$isArraySubdocument || schemaType.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { return; } _this.invalidate(path, err, undefined, true); } } } |
| ValidateSync | function(pathsToValidate, options) { const _this = this; if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) { options = arguments[0]; pathsToValidate = null; } const hasValidateModifiedOnlyOption = options && (typeof options === 'object') && ('validateModifiedOnly' in options); let shouldValidateModifiedOnly; if (hasValidateModifiedOnlyOption) { shouldValidateModifiedOnly = !!options.validateModifiedOnly; } else { shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly; } let pathsToSkip = options?.pathsToSkip; const validateAllPaths = options?.validateAllPaths; if (validateAllPaths) { if (pathsToSkip) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`'); } if (pathsToValidate) { throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`'); } } if (typeof pathsToValidate === 'string') { const isOnePathOnly = pathsToValidate.indexOf(' ') === -1; pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' '); } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) { pathsToSkip = pathsToSkip.split(' '); } // only validate required fields when necessary let paths; let skipSchemaValidators; if (validateAllPaths) { paths = new Set(Object.keys(this.$__schema.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 schemaType = this.$__schema.path(path); if (!schemaType?.$isMongooseArray) { continue; } const val = this.$__getValue(path); if (!val) { continue; } _pushNestedArrayPaths(val, paths, path); } paths = [...paths]; skipSchemaValidators = {}; } else { const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip); paths = shouldValidateModifiedOnly ? pathDetails[0].filter((path) => this.$isModified(path)) : pathDetails[0]; skipSchemaValidators = pathDetails[1]; } const validating = {}; for (let i = 0, len = paths.length; i < len; ++i) { const path = paths[i]; if (validating[path]) { continue; } validating[path] = true; const p = _this.$__schema.path(path); if (!p) { continue; } if (!_this.$isValid(path)) { continue; } const val = _this.$__getValue(path); const err = p.doValidateSync(val, _this, { skipSchemaValidators: skipSchemaValidators[path], path: path, validateModifiedOnly: shouldValidateModifiedOnly, validateAllPaths }); if (err) { const isSubdoc = p.$isSingleNested || p.$isArraySubdocument || p.$isMongooseDocumentArray; if (isSubdoc && err instanceof ValidationError) { continue; } _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; } |
| $ Reset | function reset() { let _this = this; // Skip for subdocuments const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null; if (subdocs?.length > 0) { for (const subdoc of subdocs) { subdoc.$__reset(); } } // clear atomics this.$__dirty().forEach(function(dirt) { const type = dirt.value; if (type && typeof type.clearAtomics === 'function') { type.clearAtomics(); } else if (type && type[arrayAtomicsSymbol]) { type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; type[arrayAtomicsSymbol] = {}; } }); this.$__.backup = {}; this.$__.backup.activePaths = { modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')), default: Object.assign({}, this.$__.activePaths.getStatePaths('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; } |
| $ UndoReset | function $__undoReset() { if (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?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; } } if (!this.$isSubdocument) { for (const subdoc of this.$getAllSubdocs()) { subdoc.$__undoReset(); } } } |
| $ 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) }; })); const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value])); // Ignore "foo.a" if "foo" is dirty already. const minimal = []; all.forEach(function(item) { if (!item) { return; } let top = null; const array = parentPaths(item.path); for (let i = 0; i < array.length - 1; i++) { if (allPaths.has(array[i])) { top = allPaths.get(array[i]); break; } } if (top == null) { minimal.push(item); } else if (top != null && top[arrayAtomicsSymbol] != null && top.hasAtomics()) { // special case for top level MongooseArrays // the `top` array itself and a sub path of `top` are being set. // the only way to honor all of both modifications is through a $set // of entire array. top[arrayAtomicsSymbol] = {}; top[arrayAtomicsSymbol].$set = top; } }); return minimal; } |
| $ SetSchema | function(schema) { 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(); } if (schema.path('schema') == null) { this.schema = schema; } this.$__schema = schema; this.$__middleware = schema._getDocumentMiddleware(); this[documentSchemaSymbol] = schema; } |
| $ 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 && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length; }).reduce(function(seed, array) { return seed.concat(array); }, []) .filter(function(doc) { return doc; }); } |
| $GetAllSubdocs | function(options) { if (options?.useCache && this.$__.saveOptions?.__subdocs) { return this.$__.saveOptions.__subdocs; } DocumentArray || (DocumentArray = require('./types/documentArray')); Embedded = Embedded || require('./types/arraySubdocument'); const subDocs = []; function getSubdocs(doc) { const newSubdocs = []; for (const { model } of doc.$__schema.childSchemas) { // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose const val = doc.$__getValue(model.path); if (val == null) { continue; } if (val.$__) { newSubdocs.push(val); } if (Array.isArray(val)) { for (const el of val) { if (el?.$__) { newSubdocs.push(el); } } } if (val instanceof Map) { for (const el of val.values()) { if (el?.$__) { newSubdocs.push(el); } } } } for (const subdoc of newSubdocs) { getSubdocs(subdoc); } subDocs.push(...newSubdocs); } getSubdocs(this); if (this.$__.saveOptions) { this.$__.saveOptions.__subdocs = subDocs; } return subDocs; } |
| $ HandleReject | function handleReject(err) { // emit on the Model if listening if (this.$listeners('error').length) { this.$emit('error', err); } else if (this.constructor.listeners?.('error').length) { this.constructor.emit('error', err); } } |
| $ ToObjectShallow | function $__toObjectShallow(schemaFieldsOnly) { const ret = {}; if (this._doc != null) { const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc); for (const key of keys) { // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths const value = this._doc[key]; if (value instanceof Date) { ret[key] = new Date(value); } else if (value !== undefined) { ret[key] = value; } } } return ret; } |
| ToObject | function(options) { return this.$toObject(options); } |
| ToJSON | function(options) { return this.$toObject(options, true); } |
| ToString | function() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); } |
| 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; } |
| $GetPopulatedDocs | function $getPopulatedDocs() { let keys = []; if (this.$__.populated != null) { keys = keys.concat(Object.keys(this.$__.populated)); } let result = []; for (const key of keys) { const value = this.$get(key); if (Array.isArray(value)) { result = result.concat(value); } else if (value instanceof Document) { result.push(value); } } return result; } |
| Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $Populated | function(path, val, options) { // val and options are internal if (val == null || val === true) { if (!this.$__.populated) { return undefined; } if (typeof path !== 'string') { return undefined; } // Map paths can be populated with either `path.$*` or just `path` const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path; const v = this.$__.populated[_path]; if (v) { return val === true ? v : v.value; } return undefined; } 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 && 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; } |
| $AssertPopulated | function $assertPopulated(path, values) { if (Array.isArray(path)) { path.forEach(p => this.$assertPopulated(p, values)); return this; } if (arguments.length > 1) { this.$set(values); } if (!this.$populated(path)) { throw new MongooseError(`Expected path "${path}" to be populated`); } return this; } |
| Depopulate | function(path) { if (typeof path === 'string') { path = path.indexOf(' ') === -1 ? [path] : path.split(' '); } let populatedIds; const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; const populated = 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]; if (Array.isArray(populatedIds)) { const arr = utils.getValue(key, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(key, populatedIds, this._doc); } } else { utils.setValue(key, populatedIds, this._doc); } } 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) { if (Array.isArray(populatedIds)) { const arr = utils.getValue(singlePath, this._doc); if (arr.isMongooseArray) { const rawArray = arr.__array; for (let i = 0; i < rawArray.length; ++i) { const subdoc = rawArray[i]; if (subdoc == null) { continue; } rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; } } else { utils.setValue(singlePath, populatedIds, this._doc); } } else { utils.setValue(singlePath, populatedIds, this._doc); } } } return this; } |
| GetChanges | function() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; } |
| $ Delta | function $__delta() { const dirty = this.$__dirty(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); if (modPaths.some(path => optCon.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else if (Array.isArray(optimisticConcurrency?.exclude)) { const excluded = new Set(optimisticConcurrency.exclude); const modPaths = this.modifiedPaths(); if (modPaths.some(path => !excluded.has(path))) { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } if (!dirty.length && VERSION_ALL !== this.$__.version) { return; } const where = {}; const delta = {}; const len = dirty.length; const divergent = []; let d = 0; where._id = this._doc._id; // If `_id` is an object, need to depopulate, but also need to be careful // because `_id` can technically be null (see gh-6406) if (where?._id?.$__ != null) { where._id = where._id.toObject({ transform: false, depopulate: true }); } for (; d < len; ++d) { const data = dirty[d]; let value = data.value; const match = checkDivergentArray(this, data.path, value); if (match) { divergent.push(match); continue; } const pop = this.$populated(data.path, true); if (!pop && this.$__.selected) { // If any array was selected using an $elemMatch projection, we alter the path and where clause // NOTE: MongoDB only supports projected $elemMatch on top level array. const pathSplit = data.path.split('.'); const top = pathSplit[0]; if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { // If the selected array entry was modified if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { where[top] = this.$__.selected[top]; pathSplit[1] = '$'; data.path = pathSplit.join('.'); } // if the selected array was modified in any other way throw an error else { divergent.push(data.path); continue; } } } // If this path is set to default, and either this path or one of // its parents is excluded, don't treat this path as dirty. if (this.$isDefault(data.path) && this.$__.selected) { if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) { continue; } const pathsToCheck = parentPaths(data.path); if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) { continue; } } if (divergent.length) continue; if (value === undefined) { operand(this, where, delta, data, 1, '$unset'); } else if (value === null) { operand(this, where, delta, data, null); } else if (typeof value.getAtomics === 'function') { // arrays and other custom container types handleAtomics(this, where, delta, data, value); } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { // MongooseBuffer value = value.toObject(); operand(this, where, delta, data, value); } else { if (this.$__.primitiveAtomics?.[data.path] != null) { const val = this.$__.primitiveAtomics[data.path]; const op = firstKey(val); operand(this, where, delta, data, val[op], op); } else { value = clone(value, { depopulate: true, transform: false, virtuals: false, getters: false, omitUndefined: true, _isNested: true }); operand(this, where, delta, data, value); } } } if (divergent.length) { throw new DivergentArrayError(divergent); } if (this.$__.version) { this.$__version(where, delta); } if (utils.hasOwnKeys(delta) === false) { return [where, null]; } return [where, delta]; } |
| $Clone | function() { const Model = this.constructor; const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc }); } if (this.$__) { const Cache = this.$__.constructor; const clonedCache = new Cache(); for (const key of Object.getOwnPropertyNames(this.$__)) { if (key === 'activePaths') { continue; } clonedCache[key] = clone(this.$__[key]); } Object.assign( clonedCache.activePaths, clone({ ...this.$__.activePaths }) ); clonedDoc.$__ = clonedCache; } return clonedDoc; } |
| $CreateModifiedPathsSnapshot | function $createModifiedPathsSnapshot() { const subdocSnapshot = new WeakMap(); if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { subdocSnapshot.set(child, child.$__.activePaths.clone()); } } return new ModifiedPathsSnapshot( subdocSnapshot, this.$__.activePaths.clone(), this.$__.version ); } |
| $RestoreModifiedPathsSnapshot | function $restoreModifiedPathsSnapshot(snapshot) { this.$__.activePaths = snapshot.activePaths.clone(); this.$__.version = snapshot.version; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { if (snapshot.subdocSnapshot.has(child)) { child.$__.activePaths = snapshot.subdocSnapshot.get(child); } } } return this; } |
| $ClearModifiedPaths | function $clearModifiedPaths() { this.$__.activePaths.clear('modify'); this.$__.activePaths.clear('init'); this.$__.version = 0; if (!this.$isSubdocument) { const subdocs = this.$getAllSubdocs(); for (const child of subdocs) { child.$clearModifiedPaths(); } } return this; } |
| $ HasOnlyPrimitiveValues | function $__hasOnlyPrimitiveValues() { return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { return v == null || typeof v !== 'object' || (utils.isNativeObject(v) && !Array.isArray(v)) || isBsonType(v, 'ObjectId') || isBsonType(v, 'Decimal128'); })); } |
| ApplyVersionIncrement | function _applyVersionIncrement() { if (!this.$__.version) return; const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); this.$__.version = undefined; if (doIncrement) { const key = this.$__schema.options.versionKey; const version = this.$__getValue(key) || 0; this.$__setValue(key, version + 1); // increment version if was successful } } |
Get personalized pricing and schedule a test drive with our expert team